/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include "clnt_subr.h" /* * Common code to create client of MOUNTPROG protocol. * Used by dfmounts, dfshares, showmount. */ CLIENT * mountprog_client_create(const char *host, struct timeval *tv) { rpcvers_t versnum; CLIENT *cl; /* * First try circuit, then drop back to datagram if * circuit is unavailable (an old version of mountd perhaps) * Using circuit is preferred because it can handle * arbitrarily long export lists. */ cl = clnt_create_vers(host, MOUNTPROG, &versnum, MOUNTVERS, MOUNTVERS3, "circuit_n"); if (cl == NULL) { if (rpc_createerr.cf_stat == RPC_PROGNOTREGISTERED) cl = clnt_create_vers(host, MOUNTPROG, &versnum, MOUNTVERS, MOUNTVERS3, "datagram_n"); if (cl == NULL) { pr_err(gettext("can't contact server: %s\n"), clnt_spcreateerror(host)); (void) __rpc_control(CLCR_SET_RPCB_TIMEOUT, tv); } } return (cl); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2023 RackTop Systems, Inc. */ #ifndef _CLNT_SUBR_H #define _CLNT_SUBR_H #include #include #ifdef __cplusplus extern "C" { #endif /* * nfs client library routines */ extern CLIENT *mountprog_client_create(const char *, struct timeval *); extern void pr_err(char *, ...); #ifdef __cplusplus } #endif #endif /* _CLNT_SUBR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* LINTLIBRARY */ /* PROTOLIB1 */ /* NFS server */ #include #include #include #include #include #include #include #include #include #include #include #include /* * The parent never returns from this function. It sits * and waits for the child to send status on whether it * loaded or not. * * We do not close down the standard file descriptors until * we know the child is going to run. */ int daemonize_init(void) { int status, pfds[2]; sigset_t set, oset; pid_t pid; /* * Block all signals prior to the fork and leave them blocked in the * parent so we don't get in a situation where the parent gets SIGINT * and returns non-zero exit status and the child is actually running. * In the child, restore the signal mask once we've done our setsid(). */ (void) sigfillset(&set); (void) sigdelset(&set, SIGABRT); (void) sigprocmask(SIG_BLOCK, &set, &oset); /* * We need to do this before we open the pipe - it makes things * easier in the long run. */ closefrom(STDERR_FILENO + 1); if (pipe(pfds) == -1) { fprintf(stderr, "failed to create pipe for daemonize"); exit(SMF_EXIT_ERR_FATAL); } if ((pid = fork()) == -1) { fprintf(stderr, "failed to fork into background"); exit(SMF_EXIT_ERR_FATAL); } if (pid != 0) { (void) close(pfds[1]); if (read(pfds[0], &status, sizeof (status)) == sizeof (status)) exit(status); if (waitpid(pid, &status, 0) == pid && WIFEXITED(status)) exit(WEXITSTATUS(status)); exit(SMF_EXIT_ERR_FATAL); } /* * All child from here on... */ (void) setsid(); (void) sigprocmask(SIG_SETMASK, &oset, NULL); (void) close(pfds[0]); return (pfds[1]); } /* * We are only a daemon if the file descriptor is valid. */ void daemonize_fini(int fd) { int status = 0; if (fd != -1) { (void) write(fd, &status, sizeof (status)); (void) close(fd); if ((fd = open("/dev/null", O_RDWR)) >= 0) { (void) fcntl(fd, F_DUP2FD, STDIN_FILENO); (void) fcntl(fd, F_DUP2FD, STDOUT_FILENO); (void) fcntl(fd, F_DUP2FD, STDERR_FILENO); (void) close(fd); } } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Helper routines for nfsmapid and autod daemon * to translate hostname to IP address and Netinfo. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_resolve.h" void free_knconf(struct knetconfig *k) { if (k == NULL) return; if (k->knc_protofmly) free(k->knc_protofmly); if (k->knc_proto) free(k->knc_proto); free(k); } struct knetconfig * get_knconf(struct netconfig *nconf) { struct stat stbuf; struct knetconfig *k = NULL; int len; if (stat(nconf->nc_device, &stbuf) < 0) { syslog(LOG_ERR, "get_knconf: stat %s: %m", nconf->nc_device); return (NULL); } k = (struct knetconfig *)malloc(sizeof (*k)); if (k == NULL) goto nomem; k->knc_semantics = nconf->nc_semantics; len = strlen(nconf->nc_protofmly); if (len <= 0) goto err_out; k->knc_protofmly = malloc(KNC_STRSIZE); if (k->knc_protofmly == NULL) goto nomem; bzero(k->knc_protofmly, KNC_STRSIZE); bcopy(nconf->nc_protofmly, k->knc_protofmly, len); len = strlen(nconf->nc_proto); if (len <= 0) goto err_out; k->knc_proto = malloc(KNC_STRSIZE); if (k->knc_proto == NULL) goto nomem; bzero(k->knc_proto, KNC_STRSIZE); bcopy(nconf->nc_proto, k->knc_proto, len); k->knc_rdev = stbuf.st_rdev; return (k); nomem: syslog(LOG_ERR, "get_knconf: no memory"); err_out: if (k != NULL) (void) free_knconf(k); return (NULL); } /* * Get the information needed for an NFSv4.x referral. This * information includes the netbuf, netname and knconfig. */ struct nfs_fsl_info * get_nfs4ref_info(char *host, int port, int nfsver) { char netname[MAXNETNAMELEN + 1]; enum clnt_stat cstat; struct nfs_fsl_info *fsl_retp = NULL; struct netconfig *netconf = NULL; char *nametemp, *namex = NULL; struct netbuf *nb = NULL; NCONF_HANDLE *nc = NULL; fsl_retp = calloc(1, sizeof (struct nfs_fsl_info)); if (fsl_retp == NULL) { syslog(LOG_ERR, "get_nfs4ref_info: no memory\n"); return (NULL); } nametemp = malloc(MAXNETNAMELEN + 1); if (nametemp == NULL) { free(fsl_retp); return (NULL); } host2netname(nametemp, host, NULL); namex = calloc(1, strlen(nametemp) + 1); if (namex == NULL) { free(nametemp); free(fsl_retp); return (NULL); } strncpy(namex, nametemp, strlen(nametemp)); free(nametemp); fsl_retp->netname = namex; fsl_retp->netnm_len = strlen(namex) + 1; fsl_retp->addr = resolve_netconf(host, NFS_PROGRAM, nfsver, &netconf, port, NULL, NULL, TRUE, NULL, &cstat); if (netconf == NULL || fsl_retp->addr == NULL) goto done; fsl_retp->knconf = get_knconf(netconf); if (fsl_retp->knconf == NULL) goto done; fsl_retp->knconf_len = (sizeof (struct knetconfig) + (KNC_STRSIZE * 2)); fsl_retp->netbuf_len = (sizeof (struct netbuf) + fsl_retp->addr->maxlen); return (fsl_retp); done: free_nfs4ref_info(fsl_retp); return (NULL); } void free_nfs4ref_info(struct nfs_fsl_info *fsl_retp) { if (fsl_retp == NULL) return; free_knconf(fsl_retp->knconf); free(fsl_retp->netname); if (fsl_retp->addr != NULL) { free(fsl_retp->addr->buf); free(fsl_retp->addr); } free(fsl_retp); } void cleanup_tli_parms(struct t_bind *tbind, int fd) { if (tbind != NULL) { t_free((char *)tbind, T_BIND); tbind = NULL; } if (fd >= 0) (void) t_close(fd); fd = -1; } struct netbuf * resolve_netconf(char *host, rpcprog_t prog, rpcvers_t nfsver, struct netconfig **netconf, ushort_t port, struct t_info *tinfo, caddr_t *fhp, bool_t direct_to_server, char *fspath, enum clnt_stat *cstatp) { NCONF_HANDLE *nc; struct netconfig *nconf = NULL; int nthtry = FIRST_TRY; struct netbuf *nb; enum clnt_stat cstat; nc = setnetpath(); if (nc == NULL) goto done; retry: while (nconf = getnetpath(nc)) { if (nconf->nc_flag & NC_VISIBLE) { if (nthtry == FIRST_TRY) { if ((nconf->nc_semantics == NC_TPI_COTS_ORD) || (nconf->nc_semantics == NC_TPI_COTS)) { if (port == 0) break; if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, NC_TCP) == 0)) break; } } if (nthtry == SECOND_TRY) { if (nconf->nc_semantics == NC_TPI_CLTS) { if (port == 0) break; if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, NC_UDP) == 0)) break; } } } } /* while */ if (nconf == NULL) { if (++nthtry <= MNT_PREF_LISTLEN) { endnetpath(nc); if ((nc = setnetpath()) == NULL) goto done; goto retry; } else return (NULL); } else { nb = get_server_addr(host, NFS_PROGRAM, nfsver, nconf, port, NULL, NULL, TRUE, NULL, &cstat); if (cstat != RPC_SUCCESS) goto retry; } done: *netconf = nconf; *cstatp = cstat; if (nc) endnetpath(nc); return (nb); } int setup_nb_parms(struct netconfig *nconf, struct t_bind *tbind, struct t_info *tinfo, char *hostname, int fd, bool_t direct_to_server, ushort_t port, rpcprog_t prog, rpcvers_t vers, bool_t file_handle) { if (nconf == NULL) { return (-1); } if (direct_to_server == TRUE) { struct nd_hostserv hs; struct nd_addrlist *retaddrs; hs.h_host = hostname; if (port == 0) hs.h_serv = "nfs"; else hs.h_serv = NULL; if (netdir_getbyname(nconf, &hs, &retaddrs) != ND_OK) { return (-1); } memcpy(tbind->addr.buf, retaddrs->n_addrs->buf, retaddrs->n_addrs->len); tbind->addr.len = retaddrs->n_addrs->len; tbind->addr.maxlen = retaddrs->n_addrs->maxlen; netdir_free((void *)retaddrs, ND_ADDRLIST); if (port) { /* LINTED pointer alignment */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ((struct sockaddr_in *) tbind->addr.buf)->sin_port = htons((ushort_t)port); else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ((struct sockaddr_in6 *) tbind->addr.buf)->sin6_port = htons((ushort_t)port); } if (file_handle) { if (netdir_options(nconf, ND_SET_RESERVEDPORT, fd, NULL) == -1) return (-1); } } else if (!file_handle) { if (port) { /* LINTED pointer alignment */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ((struct sockaddr_in *) tbind->addr.buf)->sin_port = htons((ushort_t)port); else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ((struct sockaddr_in6 *) tbind->addr.buf)->sin6_port = htons((ushort_t)port); } } else { return (-1); } return (1); } /* * Sets up TLI interface and finds the address withe netdir_getbyname(). * returns the address returned from the call. * Caller frees up the memory allocated here. */ struct netbuf * get_server_addr(char *hostname, rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf, ushort_t port, struct t_info *tinfo, caddr_t *fhp, bool_t direct_to_server, char *fspath, enum clnt_stat *cstat) { int fd = -1; struct t_bind *tbind = NULL; enum clnt_stat cs = RPC_SYSTEMERROR; struct netbuf *nb = NULL; int ret = -1; if (prog == NFS_PROGRAM && vers == NFS_V4) if (strncasecmp(nconf->nc_proto, NC_UDP, strlen(NC_UDP)) == 0) goto done; if ((fd = t_open(nconf->nc_device, O_RDWR, tinfo)) < 0) goto done; if ((tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR)) == NULL) goto done; if (setup_nb_parms(nconf, tbind, tinfo, hostname, fd, direct_to_server, port, prog, vers, 0) < 0) goto done; nb = (struct netbuf *)malloc(sizeof (struct netbuf)); if (nb == NULL) { syslog(LOG_ERR, "no memory\n"); goto done; } nb->buf = (char *)malloc(tbind->addr.maxlen); if (nb->buf == NULL) { syslog(LOG_ERR, "no memory\n"); free(nb); nb = NULL; goto done; } (void) memcpy(nb->buf, tbind->addr.buf, tbind->addr.len); nb->len = tbind->addr.len; nb->maxlen = tbind->addr.maxlen; cs = RPC_SUCCESS; done: *cstat = cs; cleanup_tli_parms(tbind, fd); return (nb); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NFS_RESOLVE_H #define _NFS_RESOLVE_H /* number of transports to try */ #define MNT_PREF_LISTLEN 2 #define FIRST_TRY 1 #define SECOND_TRY 2 extern struct knetconfig *get_knconf(struct netconfig *); extern void free_knconf(struct knetconfig *); extern bool_t xdr_nfs_fsl_info(XDR *, struct nfs_fsl_info *); extern struct netconfig *get_netconfig(NCONF_HANDLE *, ushort_t, char *); extern int setup_nb_parms(struct netconfig *, struct t_bind *, struct t_info *, char *, int, bool_t, ushort_t, rpcprog_t, rpcvers_t, int); extern void cleanup_tli_parms(struct t_bind *, int); extern struct nfs_fsl_info *get_nfs4ref_info(char *, int, int); extern void free_nfs4ref_info(struct nfs_fsl_info *); extern struct netbuf *get_server_addr(char *, rpcprog_t, rpcvers_t, struct netconfig *, ushort_t, struct t_info *, caddr_t *, bool_t, char *, enum clnt_stat *); extern struct netbuf *resolve_netconf(char *, rpcprog_t, rpcvers_t, struct netconfig **, ushort_t, struct t_info *, caddr_t *, bool_t, char *, enum clnt_stat *); #endif /* _NFS_RESOLVE_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* LINTLIBRARY */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * nfs security related library routines. * * Some of the routines in this file are adopted from * lib/libnsl/netselect/netselect.c and are modified to be * used for accessing /etc/nfssec.conf. */ /* SVr4.0 1.18 */ #include #include #include #include #include #include #include #include #include #ifdef WNFS_SEC_NEGO #include "webnfs.h" #endif #define GETBYNAME 1 #define GETBYNUM 2 /* * mapping for /etc/nfssec.conf */ struct sc_data { char *string; int value; }; static struct sc_data sc_service[] = { "default", rpc_gss_svc_default, "-", rpc_gss_svc_none, "none", rpc_gss_svc_none, "integrity", rpc_gss_svc_integrity, "privacy", rpc_gss_svc_privacy, NULL, SC_FAILURE }; static mutex_t matching_lock = DEFAULTMUTEX; static char *gettoken(char *, int); extern int atoi(const char *str); extern bool_t rpc_gss_get_principal_name(rpc_gss_principal_t *, char *, char *, char *, char *); extern bool_t rpc_gss_mech_to_oid(char *, rpc_gss_OID *); extern bool_t rpc_gss_qop_to_num(char *, char *, uint_t *); /* * blank() returns true if the line is a blank line, 0 otherwise */ static int blank(char *cp) { while (*cp && isspace(*cp)) { cp++; } return (*cp == '\0'); } /* * comment() returns true if the line is a comment, 0 otherwise. */ static int comment(char *cp) { while (*cp && isspace(*cp)) { cp++; } return (*cp == '#'); } /* * getvalue() searches for the given string in the given array, * and returns the integer value associated with the string. */ static unsigned long getvalue(char *cp, struct sc_data sc_data[]) { int i; /* used to index through the given struct sc_data array */ for (i = 0; sc_data[i].string; i++) { if (strcmp(sc_data[i].string, cp) == 0) { break; } } return (sc_data[i].value); } /* * shift1left() moves all characters in the string over 1 to * the left. */ static void shift1left(char *p) { for (; *p; p++) *p = *(p + 1); } /* * gettoken() behaves much like strtok(), except that * it knows about escaped space characters (i.e., space characters * preceeded by a '\' are taken literally). * * XXX We should make this MT-hot by making it more like strtok_r(). */ static char * gettoken(char *cp, int skip) { static char *savep; /* the place where we left off */ register char *p; /* the beginning of the new token */ register char *retp; /* the token to be returned */ /* Determine if first or subsequent call */ p = (cp == NULL)? savep: cp; /* Return if no tokens remain. */ if (p == 0) { return (NULL); } while (isspace(*p)) p++; if (*p == '\0') { return (NULL); } /* * Save the location of the token and then skip past it */ retp = p; while (*p) { if (isspace(*p)) { if (skip == TRUE) { shift1left(p); continue; } else break; } /* * Only process the escape of the space separator; * since the token may contain other separators, * let the other routines handle the escape of * specific characters in the token. */ if (*p == '\\' && *(p + 1) != '\n' && isspace(*(p + 1))) { shift1left(p); } p++; } if (*p == '\0') { savep = 0; /* indicate this is last token */ } else { *p = '\0'; savep = ++p; } return (retp); } /* * matchname() parses a line of the /etc/nfssec.conf file * and match the sc_name with the given name. * If there is a match, it fills the information into the given * pointer of the seconfig_t structure. * * Returns TRUE if a match is found. */ static bool_t matchname(char *line, char *name, seconfig_t *secp) { char *tok1, *tok2; /* holds a token from the line */ char *secname, *gss_mech, *gss_qop; /* pointer to a secmode name */ if ((secname = gettoken(line, FALSE)) == NULL) { /* bad line */ return (FALSE); } if (strcmp(secname, name) != 0) { return (FALSE); } tok1 = tok2 = NULL; if (((tok1 = gettoken(NULL, FALSE)) == NULL) || ((gss_mech = gettoken(NULL, FALSE)) == NULL) || ((gss_qop = gettoken(NULL, FALSE)) == NULL) || ((tok2 = gettoken(NULL, FALSE)) == NULL) || ((secp->sc_service = getvalue(tok2, sc_service)) == SC_FAILURE)) { return (FALSE); } secp->sc_nfsnum = atoi(tok1); (void) strcpy(secp->sc_name, secname); (void) strcpy(secp->sc_gss_mech, gss_mech); secp->sc_gss_mech_type = NULL; if (secp->sc_gss_mech[0] != '-') { if (!rpc_gss_mech_to_oid(gss_mech, &secp->sc_gss_mech_type) || !rpc_gss_qop_to_num(gss_qop, gss_mech, &secp->sc_qop)) { return (FALSE); } } return (TRUE); } /* * matchnum() parses a line of the /etc/nfssec.conf file * and match the sc_nfsnum with the given number. * If it is a match, it fills the information in the given pointer * of the seconfig_t structure. * * Returns TRUE if a match is found. */ static bool_t matchnum(char *line, int num, seconfig_t *secp) { char *tok1, *tok2; /* holds a token from the line */ char *secname, *gss_mech, *gss_qop; /* pointer to a secmode name */ if ((secname = gettoken(line, FALSE)) == NULL) { /* bad line */ return (FALSE); } tok1 = tok2 = NULL; if ((tok1 = gettoken(NULL, FALSE)) == NULL) { /* bad line */ return (FALSE); } if ((secp->sc_nfsnum = atoi(tok1)) != num) { return (FALSE); } if (((gss_mech = gettoken(NULL, FALSE)) == NULL) || ((gss_qop = gettoken(NULL, FALSE)) == NULL) || ((tok2 = gettoken(NULL, FALSE)) == NULL) || ((secp->sc_service = getvalue(tok2, sc_service)) == SC_FAILURE)) { return (FALSE); } (void) strcpy(secp->sc_name, secname); (void) strcpy(secp->sc_gss_mech, gss_mech); if (secp->sc_gss_mech[0] != '-') { if (!rpc_gss_mech_to_oid(gss_mech, &secp->sc_gss_mech_type) || !rpc_gss_qop_to_num(gss_qop, gss_mech, &secp->sc_qop)) { return (FALSE); } } return (TRUE); } /* * Fill in the RPC Protocol security flavor number * into the sc_rpcnum of seconfig_t structure. * * Mainly to map NFS secmod number to RPCSEC_GSS if * a mechanism name is specified. */ static void get_rpcnum(seconfig_t *secp) { if (secp->sc_gss_mech[0] != '-') { secp->sc_rpcnum = RPCSEC_GSS; } else { secp->sc_rpcnum = secp->sc_nfsnum; } } /* * Parse a given hostname (nodename[.domain@realm]) to * instant name (nodename[.domain]) and realm. * * Assuming user has allocated the space for inst and realm. */ static int parsehostname(char *hostname, char *inst, char *realm) { char *h, *r; if (!hostname) return (0); h = (char *)strdup(hostname); if (!h) { syslog(LOG_ERR, "parsehostname: no memory\n"); return (0); } r = (char *)strchr(h, '@'); if (!r) { (void) strcpy(inst, h); (void) strcpy(realm, ""); } else { *r++ = '\0'; (void) strcpy(inst, h); (void) strcpy(realm, r); } free(h); return (1); } /* * Get the name corresponding to a qop num. */ char * nfs_get_qop_name(seconfig_t *entryp) { char *tok; /* holds a token from the line */ char *secname, *gss_qop = NULL; /* pointer to a secmode name */ char line[BUFSIZ]; /* holds each line of NFSSEC_CONF */ FILE *fp; /* file stream for NFSSEC_CONF */ (void) mutex_lock(&matching_lock); if ((fp = fopen(NFSSEC_CONF, "r")) == NULL) { (void) mutex_unlock(&matching_lock); return (NULL); } while (fgets(line, BUFSIZ, fp)) { if (!(blank(line) || comment(line))) { if ((secname = gettoken(line, FALSE)) == NULL) { /* bad line */ continue; } if (strcmp(secname, entryp->sc_name) == 0) { tok = NULL; if ((tok = gettoken(NULL, FALSE)) == NULL) { /* bad line */ goto err; } if (atoi(tok) != entryp->sc_nfsnum) goto err; if ((gettoken(NULL, FALSE) == NULL) || ((gss_qop = gettoken(NULL, FALSE)) == NULL)) { goto err; } break; } } } err: (void) fclose(fp); (void) mutex_unlock(&matching_lock); return (gss_qop); } /* * This routine creates an auth handle assocaited with the * negotiated security flavor contained in nfs_sec. The auth * handle will be used in the next LOOKUP request to fetch * the filehandle. */ AUTH * nfs_create_ah(CLIENT *cl, char *hostname, seconfig_t *nfs_sec) { char netname[MAXNETNAMELEN+1]; char svc_name[MAXNETNAMELEN+1]; char *gss_qop; static int window = 60; if (nfs_sec == NULL) goto err; switch (nfs_sec->sc_rpcnum) { case AUTH_UNIX: case AUTH_NONE: return (NULL); case AUTH_DES: if (!host2netname(netname, hostname, NULL)) goto err; return (authdes_seccreate(netname, window, hostname, NULL)); case RPCSEC_GSS: if (cl == NULL) goto err; if (nfs_sec->sc_gss_mech_type == NULL) { syslog(LOG_ERR, "nfs_create_ah: need mechanism information\n"); goto err; } /* * RPCSEC_GSS service names are of the form svc@host.dom */ (void) sprintf(svc_name, "nfs@%s", hostname); gss_qop = nfs_get_qop_name(nfs_sec); if (gss_qop == NULL) goto err; return (rpc_gss_seccreate(cl, svc_name, nfs_sec->sc_gss_mech, nfs_sec->sc_service, gss_qop, NULL, NULL)); default: syslog(LOG_ERR, "nfs_create_ah: unknown flavor\n"); return (NULL); } err: syslog(LOG_ERR, "nfs_create_ah: failed to make auth handle\n"); return (NULL); } #ifdef WNFS_SEC_NEGO /* * This routine negotiates sec flavors with server and returns: * SNEGO_SUCCESS: successful; sec flavors are * returned in snego, * SNEGO_DEF_VALID: default sec flavor valid; no need * to negotiate flavors, * SNEGO_ARRAY_TOO_SMALL: array too small, * SNEGO_FAILURE: failure */ /* * The following depicts how sec flavors are placed in an * overloaded V2 fhandle: * * Note that the first four octets contain the length octet, * the status octet, and two padded octets to make them XDR * four-octet aligned. * * 1 2 3 4 32 * +---+---+---+---+---+---+---+---+ +---+---+---+---+ +---+ * | l | s | | | sec_1 |...| sec_n |...| | * +---+---+---+---+---+---+---+---+ +---+---+---+---+ +---+ * * where * * the status octet s indicates whether there are more security * flavors(1 means yes, 0 means no) that require the client to * perform another 0x81 LOOKUP to get them, * * the length octet l is the length describing the number of * valid octets that follow. (l = 4 * n, where n is the number * * The following depicts how sec flavors are placed in an * overloaded V3 fhandle: * * 1 4 * +--+--+--+--+ * | len | * +--+--+--+--+ * up to 64 * +--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+ * |s | | | | sec_1 | sec_2 | ... | sec_n | * +--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+ * * len = 4 * (n+1), where n is the number of security flavors * sent in the current overloaded filehandle. * * the status octet s indicates whether there are more security * mechanisms(1 means yes, 0 means no) that require the client * to perform another 0x81 LOOKUP to get them. * * Three octets are padded after the status octet. */ enum snego_stat nfs_sec_nego(rpcprog_t vers, CLIENT *clnt, char *fspath, struct snego_t *snego) { enum clnt_stat rpc_stat; static int MAX_V2_CNT = (WNL_FHSIZE/sizeof (int)) - 1; static int MAX_V3_CNT = (WNL3_FHSIZE/sizeof (int)) - 1; static struct timeval TIMEOUT = { 25, 0 }; int status; if (clnt == NULL || fspath == NULL || snego == NULL) return (SNEGO_FAILURE); if (vers == WNL_V2) { wnl_diropargs arg; wnl_diropres clnt_res; memset((char *)&arg.dir, 0, sizeof (wnl_fh)); arg.name = fspath; memset((char *)&clnt_res, 0, sizeof (clnt_res)); rpc_stat = clnt_call(clnt, WNLPROC_LOOKUP, (xdrproc_t)xdr_wnl_diropargs, (caddr_t)&arg, (xdrproc_t)xdr_wnl_diropres, (caddr_t)&clnt_res, TIMEOUT); if (rpc_stat == RPC_SUCCESS && clnt_res.status == WNL_OK) return (SNEGO_DEF_VALID); if (rpc_stat != RPC_AUTHERROR) return (SNEGO_FAILURE); { struct rpc_err e; wnl_diropres res; char *p; int tot = 0; CLNT_GETERR(clnt, &e); if (e.re_why != AUTH_TOOWEAK) return (SNEGO_FAILURE); if ((p = malloc(strlen(fspath)+3)) == NULL) { syslog(LOG_ERR, "no memory\n"); return (SNEGO_FAILURE); } /* * Do an x81 LOOKUP */ p[0] = (char)WNL_SEC_NEGO; strcpy(&p[2], fspath); do { p[1] = (char)(1+snego->cnt); /* sec index */ arg.name = p; memset((char *)&res, 0, sizeof (wnl_diropres)); if (wnlproc_lookup_2(&arg, &res, clnt) != RPC_SUCCESS || res.status != WNL_OK) { free(p); return (SNEGO_FAILURE); } /* * retrieve flavors from filehandle: * 1st byte: length * 2nd byte: status * 3rd & 4th: pad * 5th and after: sec flavors. */ { char *c = (char *)&res.wnl_diropres_u. wnl_diropres.file; int ii; int cnt = ((int)*c)/sizeof (uint_t); /* LINTED pointer alignment */ int *ip = (int *)(c+sizeof (int)); tot += cnt; if (tot >= MAX_FLAVORS) { free(p); return (SNEGO_ARRAY_TOO_SMALL); } status = (int)*(c+1); if (cnt > MAX_V2_CNT || cnt < 0) { free(p); return (SNEGO_FAILURE); } for (ii = 0; ii < cnt; ii++) snego->array[snego->cnt+ii] = ntohl(*(ip+ii)); snego->cnt += cnt; } } while (status); free(p); return (SNEGO_SUCCESS); } } else if (vers == WNL_V3) { WNL_LOOKUP3args arg; WNL_LOOKUP3res clnt_res; memset((char *)&arg.what.dir, 0, sizeof (wnl_fh3)); arg.what.name = fspath; arg.what.dir.data.data_len = 0; arg.what.dir.data.data_val = 0; memset((char *)&clnt_res, 0, sizeof (clnt_res)); rpc_stat = clnt_call(clnt, WNLPROC3_LOOKUP, (xdrproc_t)xdr_WNL_LOOKUP3args, (caddr_t)&arg, (xdrproc_t)xdr_WNL_LOOKUP3res, (caddr_t)&clnt_res, TIMEOUT); if (rpc_stat == RPC_SUCCESS && clnt_res.status == WNL3_OK) return (SNEGO_DEF_VALID); if (rpc_stat != RPC_AUTHERROR) return (SNEGO_FAILURE); { struct rpc_err e; WNL_LOOKUP3res res; char *p; int tot = 0; CLNT_GETERR(clnt, &e); if (e.re_why != AUTH_TOOWEAK) return (SNEGO_FAILURE); if ((p = malloc(strlen(fspath)+3)) == NULL) { syslog(LOG_ERR, "no memory\n"); return (SNEGO_FAILURE); } /* * Do an x81 LOOKUP */ p[0] = (char)WNL_SEC_NEGO; strcpy(&p[2], fspath); do { p[1] = (char)(1+snego->cnt); /* sec index */ arg.what.name = p; memset((char *)&res, 0, sizeof (WNL_LOOKUP3res)); if (wnlproc3_lookup_3(&arg, &res, clnt) != RPC_SUCCESS || res.status != WNL3_OK) { free(p); return (SNEGO_FAILURE); } /* * retrieve flavors from filehandle: * * 1st byte: status * 2nd thru 4th: pad * 5th and after: sec flavors. */ { char *c = res.WNL_LOOKUP3res_u. res_ok.object.data.data_val; int ii; int len = res.WNL_LOOKUP3res_u.res_ok. object.data.data_len; int cnt; /* LINTED pointer alignment */ int *ip = (int *)(c+sizeof (int)); cnt = len/sizeof (uint_t) - 1; tot += cnt; if (tot >= MAX_FLAVORS) { free(p); return (SNEGO_ARRAY_TOO_SMALL); } status = (int)(*c); if (cnt > MAX_V3_CNT || cnt < 0) { free(p); return (SNEGO_FAILURE); } for (ii = 0; ii < cnt; ii++) snego->array[snego->cnt+ii] = ntohl(*(ip+ii)); snego->cnt += cnt; } } while (status); free(p); return (SNEGO_SUCCESS); } } return (SNEGO_FAILURE); } #endif /* * Get seconfig from /etc/nfssec.conf by name or by number or * by descriptior. */ /* ARGSUSED */ static int get_seconfig(int whichway, char *name, int num, rpc_gss_service_t service, seconfig_t *entryp) { char line[BUFSIZ]; /* holds each line of NFSSEC_CONF */ FILE *fp; /* file stream for NFSSEC_CONF */ if ((whichway == GETBYNAME) && (name == NULL)) return (SC_NOTFOUND); (void) mutex_lock(&matching_lock); if ((fp = fopen(NFSSEC_CONF, "r")) == NULL) { (void) mutex_unlock(&matching_lock); return (SC_OPENFAIL); } while (fgets(line, BUFSIZ, fp)) { if (!(blank(line) || comment(line))) { switch (whichway) { case GETBYNAME: if (matchname(line, name, entryp)) { goto found; } break; case GETBYNUM: if (matchnum(line, num, entryp)) { goto found; } break; default: break; } } } (void) fclose(fp); (void) mutex_unlock(&matching_lock); return (SC_NOTFOUND); found: (void) fclose(fp); (void) mutex_unlock(&matching_lock); (void) get_rpcnum(entryp); return (SC_NOERROR); } /* * NFS project private API. * Get a seconfig entry from /etc/nfssec.conf by nfs specific sec name, * e.g. des, krb5p, etc. */ int nfs_getseconfig_byname(char *secmode_name, seconfig_t *entryp) { if (!entryp) return (SC_NOMEM); return (get_seconfig(GETBYNAME, secmode_name, 0, rpc_gss_svc_none, entryp)); } /* * NFS project private API. * * Get a seconfig entry from /etc/nfssec.conf by nfs specific sec number, * e.g. AUTH_DES, AUTH_KRB5_P, etc. */ int nfs_getseconfig_bynumber(int nfs_secnum, seconfig_t *entryp) { if (!entryp) return (SC_NOMEM); return (get_seconfig(GETBYNUM, NULL, nfs_secnum, rpc_gss_svc_none, entryp)); } /* * NFS project private API. * * Get a seconfig_t entry used as the default for NFS operations. * The default flavor entry is defined in /etc/nfssec.conf. * * Assume user has allocate spaces for secp. */ int nfs_getseconfig_default(seconfig_t *secp) { if (secp == NULL) return (SC_NOMEM); return (nfs_getseconfig_byname("default", secp)); } /* * NFS project private API. * * Free an sec_data structure. * Free the parts that nfs_clnt_secdata allocates. */ void nfs_free_secdata(sec_data_t *secdata) { dh_k4_clntdata_t *dkdata; gss_clntdata_t *gdata; if (!secdata) return; switch (secdata->rpcflavor) { case AUTH_UNIX: case AUTH_NONE: break; case AUTH_DES: /* LINTED pointer alignment */ dkdata = (dh_k4_clntdata_t *)secdata->data; if (dkdata) { if (dkdata->netname) free(dkdata->netname); if (dkdata->syncaddr.buf) free(dkdata->syncaddr.buf); free(dkdata); } break; case RPCSEC_GSS: /* LINTED pointer alignment */ gdata = (gss_clntdata_t *)secdata->data; if (gdata) { if (gdata->mechanism.elements) free(gdata->mechanism.elements); free(gdata); } break; default: break; } free(secdata); } /* * Make an client side sec_data structure and fill in appropriate value * based on its rpc security flavor. * * It is caller's responsibility to allocate space for seconfig_t, * and this routine will allocate space for the sec_data structure * and related data field. * * Return the sec_data_t on success. * If fail, return NULL pointer. */ sec_data_t * nfs_clnt_secdata(seconfig_t *secp, char *hostname, struct knetconfig *knconf, struct netbuf *syncaddr, int flags) { char netname[MAXNETNAMELEN+1]; sec_data_t *secdata; dh_k4_clntdata_t *dkdata; gss_clntdata_t *gdata; secdata = malloc(sizeof (sec_data_t)); if (!secdata) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); return (NULL); } (void) memset(secdata, 0, sizeof (sec_data_t)); secdata->secmod = secp->sc_nfsnum; secdata->rpcflavor = secp->sc_rpcnum; secdata->uid = secp->sc_uid; secdata->flags = flags; /* * Now, fill in the information for client side secdata : * * For AUTH_UNIX, AUTH_DES * hostname can be in the form of * nodename or * nodename.domain * * For RPCSEC_GSS security flavor * hostname can be in the form of * nodename or * nodename.domain or * nodename@realm (realm can be the same as the domain) or * nodename.domain@realm */ switch (secp->sc_rpcnum) { case AUTH_UNIX: case AUTH_NONE: secdata->data = NULL; break; case AUTH_DES: /* * If hostname is in the format of host.nisdomain * the netname will be constructed with * this nisdomain name rather than the default * domain of the machine. */ if (!host2netname(netname, hostname, NULL)) { syslog(LOG_ERR, "host2netname: %s: unknown\n", hostname); goto err_out; } dkdata = malloc(sizeof (dh_k4_clntdata_t)); if (!dkdata) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) memset((char *)dkdata, 0, sizeof (dh_k4_clntdata_t)); if ((dkdata->netname = strdup(netname)) == NULL) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } dkdata->netnamelen = strlen(netname); dkdata->knconf = knconf; dkdata->syncaddr = *syncaddr; dkdata->syncaddr.buf = malloc(syncaddr->len); if (dkdata->syncaddr.buf == NULL) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) memcpy(dkdata->syncaddr.buf, syncaddr->buf, syncaddr->len); secdata->data = (caddr_t)dkdata; break; case RPCSEC_GSS: if (secp->sc_gss_mech_type == NULL) { syslog(LOG_ERR, "nfs_clnt_secdata: need mechanism information\n"); goto err_out; } gdata = malloc(sizeof (gss_clntdata_t)); if (!gdata) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) strcpy(gdata->uname, "nfs"); if (!parsehostname(hostname, gdata->inst, gdata->realm)) { syslog(LOG_ERR, "nfs_clnt_secdata: bad host name\n"); goto err_out; } gdata->mechanism.length = secp->sc_gss_mech_type->length; if (!(gdata->mechanism.elements = malloc(secp->sc_gss_mech_type->length))) { syslog(LOG_ERR, "nfs_clnt_secdata: no memory\n"); goto err_out; } (void) memcpy(gdata->mechanism.elements, secp->sc_gss_mech_type->elements, secp->sc_gss_mech_type->length); gdata->qop = secp->sc_qop; gdata->service = secp->sc_service; secdata->data = (caddr_t)gdata; break; default: syslog(LOG_ERR, "nfs_clnt_secdata: unknown flavor\n"); goto err_out; } return (secdata); err_out: free(secdata); return (NULL); } /* * nfs_get_root_principal() maps a host name to its principal name * based on the given security information. * * input : seconfig - security configuration information * host - the host name which could be in the following forms: * node * node.namedomain * node@secdomain (e.g. kerberos realm is a secdomain) * node.namedomain@secdomain * output : rootname_p - address of the principal name for the host * * Currently, this routine is only used by share program. * */ bool_t nfs_get_root_principal(seconfig_t *seconfig, char *host, caddr_t *rootname_p) { char netname[MAXNETNAMELEN+1], node[MAX_NAME_LEN]; char secdomain[MAX_NAME_LEN]; rpc_gss_principal_t gssname; switch (seconfig->sc_rpcnum) { case AUTH_DES: if (!host2netname(netname, host, NULL)) { syslog(LOG_ERR, "nfs_get_root_principal: unknown host: %s\n", host); return (FALSE); } *rootname_p = strdup(netname); if (!*rootname_p) { syslog(LOG_ERR, "nfs_get_root_principal: no memory\n"); return (FALSE); } break; case RPCSEC_GSS: if (!parsehostname(host, node, secdomain)) { syslog(LOG_ERR, "nfs_get_root_principal: bad host name\n"); return (FALSE); } if (!rpc_gss_get_principal_name(&gssname, seconfig->sc_gss_mech, "root", node, secdomain)) { syslog(LOG_ERR, "nfs_get_root_principal: can not get principal name : %s\n", host); return (FALSE); } *rootname_p = (caddr_t)gssname; break; default: return (FALSE); } return (TRUE); } /* * SYSLOG SC_* errors. */ int nfs_syslog_scerr(int scerror, char msg[]) { switch (scerror) { case SC_NOMEM : (void) sprintf(msg, "%s : no memory", NFSSEC_CONF); return (0); case SC_OPENFAIL : (void) sprintf(msg, "can not open %s", NFSSEC_CONF); return (0); case SC_NOTFOUND : (void) sprintf(msg, "has no entry in %s", NFSSEC_CONF); return (0); case SC_BADENTRIES : (void) sprintf(msg, "bad entry in %s", NFSSEC_CONF); return (0); default: msg[0] = '\0'; return (-1); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include "nfs_subr.h" #include #include #include #include "smfcfg.h" #include extern int _nfssys(enum nfssys_op, void *); /* * This function is added to detect compatibility problem with SunOS4.x. * The compatibility problem exists when fshost cannot decode the request * arguments for NLM_GRANTED procedure. * Only in this case we use local locking. * In any other case we use fshost's lockd for remote file locking. * Return value: 1 if we should use local locking, 0 if not. */ int remote_lock(char *fshost, caddr_t fh) { nlm_testargs rlm_args; nlm_res rlm_res; struct timeval timeout = { 5, 0}; CLIENT *cl; enum clnt_stat rpc_stat; struct utsname myid; (void) memset((char *)&rlm_args, 0, sizeof (nlm_testargs)); (void) memset((char *)&rlm_res, 0, sizeof (nlm_res)); /* * Assign the hostname and the file handle for the * NLM_GRANTED request below. If for some reason the uname call fails, * list the server as the caller so that caller_name has some * reasonable value. */ if (uname(&myid) == -1) { rlm_args.alock.caller_name = fshost; } else { rlm_args.alock.caller_name = myid.nodename; } rlm_args.alock.fh.n_len = sizeof (fhandle_t); rlm_args.alock.fh.n_bytes = fh; cl = clnt_create(fshost, NLM_PROG, NLM_VERS, "datagram_v"); if (cl == NULL) return (0); rpc_stat = clnt_call(cl, NLM_GRANTED, xdr_nlm_testargs, (caddr_t)&rlm_args, xdr_nlm_res, (caddr_t)&rlm_res, timeout); clnt_destroy(cl); return (rpc_stat == RPC_CANTDECODEARGS); } #define fromhex(c) ((c >= '0' && c <= '9') ? (c - '0') : \ ((c >= 'A' && c <= 'F') ? (c - 'A' + 10) :\ ((c >= 'a' && c <= 'f') ? (c - 'a' + 10) : 0))) /* * The implementation of URLparse guarantees that the final string will * fit in the original one. Replaces '%' occurrences followed by 2 characters * with its corresponding hexadecimal character. */ void URLparse(char *str) { char *p, *q; p = q = str; while (*p) { *q = *p; if (*p++ == '%') { if (*p) { *q = fromhex(*p) * 16; p++; if (*p) { *q += fromhex(*p); p++; } } } q++; } *q = '\0'; } /* * Convert from URL syntax to host:path syntax. */ int convert_special(char **specialp, char *host, char *oldpath, char *newpath, char *cur_special) { char *url; char *newspec; char *p; char *p1, *p2; /* * Rebuild the URL. This is necessary because parse replica * assumes that nfs: is the host name. */ url = malloc(strlen("nfs:") + strlen(oldpath) + 1); if (url == NULL) return (-1); strcpy(url, "nfs:"); strcat(url, oldpath); /* * If we haven't done any conversion yet, allocate a buffer for it. */ if (*specialp == NULL) { newspec = *specialp = strdup(cur_special); if (newspec == NULL) { free(url); return (-1); } } else { newspec = *specialp; } /* * Now find the first occurence of the URL in the special string. */ p = strstr(newspec, url); if (p == NULL) { free(url); return (-1); } p1 = p; p2 = host; /* * Overwrite the URL in the special. * * Begin with the host name. */ for (;;) { /* * Sine URL's take more room than host:path, there is * no way we should hit a null byte in the original special. */ if (*p1 == '\0') { free(url); free(*specialp); *specialp = NULL; return (-1); } if (*p2 == '\0') { break; } *p1 = *p2; p1++; p2++; } /* * Add the : separator. */ *p1 = ':'; p1++; /* * Now over write into special the path portion of host:path in */ p2 = newpath; for (;;) { if (*p1 == '\0') { free(url); free(*specialp); *specialp = NULL; return (-1); } if (*p2 == '\0') { break; } *p1 = *p2; p1++; p2++; } /* * Now shift the rest of original special into the gap created * by replacing nfs://host[:port]/path with host:path. */ p2 = p + strlen(url); for (;;) { if (*p1 == '\0') { free(url); free(*specialp); *specialp = NULL; return (-1); } if (*p2 == '\0') { break; } *p1 = *p2; p1++; p2++; } *p1 = '\0'; free(url); return (0); } #define AUTOFS_MOUNT_TIMEOUT 600 /* default min time mount will */ void set_nfsv4_ephemeral_mount_to(void) { char valbuf[6]; int bufsz = sizeof (valbuf); uint_t mount_to = AUTOFS_MOUNT_TIMEOUT; /* * Get the value from SMF */ if (autofs_smf_get_prop("timeout", valbuf, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, AUTOMOUNTD, &bufsz) == SA_OK) { const char *errstr; uint_t val = strtonum(valbuf, 0, UINT_MAX, &errstr); if (errstr == NULL) mount_to = val; } (void) _nfssys(NFS4_EPHEMERAL_MOUNT_TO, &mount_to); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * nfs_subr.h * * Copyright (c) 1996 Sun Microsystems Inc * All Rights Reserved. */ #ifndef _NFS_SUBR_H #define _NFS_SUBR_H #include #ifdef __cplusplus extern "C" { #endif /* * nfs library routines */ extern int remote_lock(char *, caddr_t); extern void URLparse(char *); extern int convert_special(char **, char *, char *, char *, char *); #ifdef __cplusplus } #endif #endif /* _NFS_SUBR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2014 Gary Mills */ /* * nfs_tbind.c, common part for nfsd and lockd. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfs_tbind.h" #include #include #include #include #include #include #include /* * Determine valid semantics for most applications. */ #define OK_TPI_TYPE(_nconf) \ (_nconf->nc_semantics == NC_TPI_CLTS || \ _nconf->nc_semantics == NC_TPI_COTS || \ _nconf->nc_semantics == NC_TPI_COTS_ORD) #define BE32_TO_U32(a) \ ((((ulong_t)((uchar_t *)a)[0] & 0xFF) << (ulong_t)24) | \ (((ulong_t)((uchar_t *)a)[1] & 0xFF) << (ulong_t)16) | \ (((ulong_t)((uchar_t *)a)[2] & 0xFF) << (ulong_t)8) | \ ((ulong_t)((uchar_t *)a)[3] & 0xFF)) /* * Number of elements to add to the poll array on each allocation. */ #define POLL_ARRAY_INC_SIZE 64 /* * Number of file descriptors by which the process soft limit may be * increased on each call to nofile_increase(0). */ #define NOFILE_INC_SIZE 64 /* * Default TCP send and receive buffer size of NFS server. */ #define NFSD_TCP_BUFSZ (1024*1024) struct conn_ind { struct conn_ind *conn_next; struct conn_ind *conn_prev; struct t_call *conn_call; }; struct conn_entry { bool_t closing; struct netconfig nc; }; /* * this file contains transport routines common to nfsd and lockd */ static int nofile_increase(int); static int reuseaddr(int); static int recvucred(int); static int anonmlp(int); static void add_to_poll_list(int, struct netconfig *); static char *serv_name_to_port_name(char *); static int bind_to_proto(char *, char *, struct netbuf **, struct netconfig **); static int bind_to_provider(char *, char *, struct netbuf **, struct netconfig **); static void conn_close_oldest(void); static boolean_t conn_get(int, struct netconfig *, struct conn_ind **); static void cots_listen_event(int, int); static int discon_get(int, struct netconfig *, struct conn_ind **); static int do_poll_clts_action(int, int); static int do_poll_cots_action(int, int); static void remove_from_poll_list(int); static int set_addrmask(int, struct netconfig *, struct netbuf *); static int is_listen_fd_index(int); static struct pollfd *poll_array; static struct conn_entry *conn_polled; static int num_conns; /* Current number of connections */ int (*Mysvc4)(int, struct netbuf *, struct netconfig *, int, struct netbuf *); static int setopt(int fd, int level, int name, int value); static int get_opt(int fd, int level, int name); static void nfslib_set_sockbuf(int fd); /* * Called to create and prepare a transport descriptor for in-kernel * RPC service. * Returns -1 on failure and a valid descriptor on success. */ int nfslib_transport_open(struct netconfig *nconf) { int fd; struct strioctl strioc; if ((nconf == (struct netconfig *)NULL) || (nconf->nc_device == (char *)NULL)) { syslog(LOG_ERR, "no netconfig device"); return (-1); } /* * Open the transport device. */ fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL); if (fd == -1) { if (t_errno == TSYSERR && errno == EMFILE && (nofile_increase(0) == 0)) { /* Try again with a higher NOFILE limit. */ fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL); } if (fd == -1) { syslog(LOG_ERR, "t_open %s failed: t_errno %d, %m", nconf->nc_device, t_errno); return (-1); } } /* * Pop timod because the RPC module must be as close as possible * to the transport. */ if (ioctl(fd, I_POP, 0) < 0) { syslog(LOG_ERR, "I_POP of timod failed: %m"); (void) t_close(fd); return (-1); } /* * Common code for CLTS and COTS transports */ if (ioctl(fd, I_PUSH, "rpcmod") < 0) { syslog(LOG_ERR, "I_PUSH of rpcmod failed: %m"); (void) t_close(fd); return (-1); } strioc.ic_cmd = RPC_SERVER; strioc.ic_dp = (char *)0; strioc.ic_len = 0; strioc.ic_timout = -1; /* Tell rpcmod to act like a server stream. */ if (ioctl(fd, I_STR, &strioc) < 0) { syslog(LOG_ERR, "rpcmod set-up ioctl failed: %m"); (void) t_close(fd); return (-1); } /* * Re-push timod so that we will still be doing TLI * operations on the descriptor. */ if (ioctl(fd, I_PUSH, "timod") < 0) { syslog(LOG_ERR, "I_PUSH of timod failed: %m"); (void) t_close(fd); return (-1); } /* * Enable options of returning the ip's for udp. */ if (strcmp(nconf->nc_netid, "udp6") == 0) __rpc_tli_set_options(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1); else if (strcmp(nconf->nc_netid, "udp") == 0) __rpc_tli_set_options(fd, IPPROTO_IP, IP_RECVDSTADDR, 1); return (fd); } static int nofile_increase(int limit) { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { syslog(LOG_ERR, "getrlimit of NOFILE failed: %m"); return (-1); } if (limit > 0) rl.rlim_cur = limit; else rl.rlim_cur += NOFILE_INC_SIZE; if (rl.rlim_cur > rl.rlim_max && rl.rlim_max != RLIM_INFINITY) rl.rlim_max = rl.rlim_cur; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { syslog(LOG_ERR, "setrlimit of NOFILE to %d failed: %m", rl.rlim_cur); return (-1); } return (0); } static void nfslib_set_sockbuf(int fd) { int curval, val; val = NFSD_TCP_BUFSZ; curval = get_opt(fd, SOL_SOCKET, SO_SNDBUF); syslog(LOG_DEBUG, "Current SO_SNDBUF value is %d", curval); if ((curval != -1) && (curval < val)) { syslog(LOG_DEBUG, "Set SO_SNDBUF option to %d", val); if (setopt(fd, SOL_SOCKET, SO_SNDBUF, val) < 0) { syslog(LOG_ERR, "couldn't set SO_SNDBUF to %d - t_errno = %d", val, t_errno); syslog(LOG_ERR, "Check and increase system-wide tcp_max_buf"); } } curval = get_opt(fd, SOL_SOCKET, SO_RCVBUF); syslog(LOG_DEBUG, "Current SO_RCVBUF value is %d", curval); if ((curval != -1) && (curval < val)) { syslog(LOG_DEBUG, "Set SO_RCVBUF option to %d", val); if (setopt(fd, SOL_SOCKET, SO_RCVBUF, val) < 0) { syslog(LOG_ERR, "couldn't set SO_RCVBUF to %d - t_errno = %d", val, t_errno); syslog(LOG_ERR, "Check and increase system-wide tcp_max_buf"); } } } int nfslib_bindit(struct netconfig *nconf, struct netbuf **addr, struct nd_hostserv *hs, int backlog) { int fd; struct t_bind *ntb; struct t_bind tb; struct nd_addrlist *addrlist; struct t_optmgmt req, resp; struct opthdr *opt; char reqbuf[128]; bool_t use_any = FALSE; bool_t gzone = TRUE; if ((fd = nfslib_transport_open(nconf)) == -1) { syslog(LOG_ERR, "cannot establish transport service over %s", nconf->nc_device); return (-1); } addrlist = (struct nd_addrlist *)NULL; /* nfs4_callback service does not used a fieed port number */ if (strcmp(hs->h_serv, "nfs4_callback") == 0) { tb.addr.maxlen = 0; tb.addr.len = 0; tb.addr.buf = 0; use_any = TRUE; gzone = (getzoneid() == GLOBAL_ZONEID); } else if (netdir_getbyname(nconf, hs, &addrlist) != 0) { syslog(LOG_ERR, "Cannot get address for transport %s host %s service %s", nconf->nc_netid, hs->h_host, hs->h_serv); (void) t_close(fd); return (-1); } if (strcmp(nconf->nc_proto, "tcp") == 0) { /* * If we're running over TCP, then set the * SO_REUSEADDR option so that we can bind * to our preferred address even if previously * left connections exist in FIN_WAIT states. * This is somewhat bogus, but otherwise you have * to wait 2 minutes to restart after killing it. */ if (reuseaddr(fd) == -1) { syslog(LOG_WARNING, "couldn't set SO_REUSEADDR option on transport"); } } else if (strcmp(nconf->nc_proto, "udp") == 0) { /* * In order to run MLP on UDP, we need to handle creds. */ if (recvucred(fd) == -1) { syslog(LOG_WARNING, "couldn't set SO_RECVUCRED option on transport"); } } /* * Make non global zone nfs4_callback port MLP */ if (use_any && is_system_labeled() && !gzone) { if (anonmlp(fd) == -1) { /* * failing to set this option means nfs4_callback * could fail silently later. So fail it with * with an error message now. */ syslog(LOG_ERR, "couldn't set SO_ANON_MLP option on transport"); (void) t_close(fd); return (-1); } } if (nconf->nc_semantics == NC_TPI_CLTS) tb.qlen = 0; else tb.qlen = backlog; /* LINTED pointer alignment */ ntb = (struct t_bind *)t_alloc(fd, T_BIND, T_ALL); if (ntb == (struct t_bind *)NULL) { syslog(LOG_ERR, "t_alloc failed: t_errno %d, %m", t_errno); (void) t_close(fd); netdir_free((void *)addrlist, ND_ADDRLIST); return (-1); } /* * XXX - what about the space tb->addr.buf points to? This should * be either a memcpy() to/from the buf fields, or t_alloc(fd,T_BIND,) * should't be called with T_ALL. */ if (addrlist) tb.addr = *(addrlist->n_addrs); /* structure copy */ if (t_bind(fd, &tb, ntb) == -1) { syslog(LOG_ERR, "t_bind failed: t_errno %d, %m", t_errno); (void) t_free((char *)ntb, T_BIND); netdir_free((void *)addrlist, ND_ADDRLIST); (void) t_close(fd); return (-1); } /* make sure we bound to the right address */ if (use_any == FALSE && (tb.addr.len != ntb->addr.len || memcmp(tb.addr.buf, ntb->addr.buf, tb.addr.len) != 0)) { syslog(LOG_ERR, "t_bind to wrong address"); (void) t_free((char *)ntb, T_BIND); netdir_free((void *)addrlist, ND_ADDRLIST); (void) t_close(fd); return (-1); } /* * Call nfs4svc_setport so that the kernel can be * informed what port number the daemon is listing * for incoming connection requests. */ if ((nconf->nc_semantics == NC_TPI_COTS || nconf->nc_semantics == NC_TPI_COTS_ORD) && Mysvc4 != NULL) (*Mysvc4)(fd, NULL, nconf, NFS4_SETPORT, &ntb->addr); *addr = &ntb->addr; netdir_free((void *)addrlist, ND_ADDRLIST); if (strcmp(nconf->nc_proto, "tcp") == 0) { /* * Disable the Nagle algorithm on TCP connections. * Connections accepted from this listener will * inherit the listener options. */ /* LINTED pointer alignment */ opt = (struct opthdr *)reqbuf; opt->level = IPPROTO_TCP; opt->name = TCP_NODELAY; opt->len = sizeof (int); /* LINTED pointer alignment */ *(int *)((char *)opt + sizeof (*opt)) = 1; req.flags = T_NEGOTIATE; req.opt.len = sizeof (*opt) + opt->len; req.opt.buf = (char *)opt; resp.flags = 0; resp.opt.buf = reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { syslog(LOG_ERR, "couldn't set NODELAY option for proto %s: t_errno = %d, %m", nconf->nc_proto, t_errno); } nfslib_set_sockbuf(fd); } return (fd); } static int get_opt(int fd, int level, int name) { struct t_optmgmt req, res; struct { struct opthdr opt; int value; } reqbuf; reqbuf.opt.level = level; reqbuf.opt.name = name; reqbuf.opt.len = sizeof (int); reqbuf.value = 0; req.flags = T_CURRENT; req.opt.len = sizeof (reqbuf); req.opt.buf = (char *)&reqbuf; res.flags = 0; res.opt.buf = (char *)&reqbuf; res.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &res) < 0 || res.flags != T_SUCCESS) { t_error("t_optmgmt"); return (-1); } return (reqbuf.value); } static int setopt(int fd, int level, int name, int value) { struct t_optmgmt req, resp; struct { struct opthdr opt; int value; } reqbuf; reqbuf.opt.level = level; reqbuf.opt.name = name; reqbuf.opt.len = sizeof (int); reqbuf.value = value; req.flags = T_NEGOTIATE; req.opt.len = sizeof (reqbuf); req.opt.buf = (char *)&reqbuf; resp.flags = 0; resp.opt.buf = (char *)&reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { t_error("t_optmgmt"); return (-1); } return (0); } static int reuseaddr(int fd) { return (setopt(fd, SOL_SOCKET, SO_REUSEADDR, 1)); } static int recvucred(int fd) { return (setopt(fd, SOL_SOCKET, SO_RECVUCRED, 1)); } static int anonmlp(int fd) { return (setopt(fd, SOL_SOCKET, SO_ANON_MLP, 1)); } void nfslib_log_tli_error(char *tli_name, int fd, struct netconfig *nconf) { int error; /* * Save the error code across syslog(), just in case syslog() * gets its own error and, therefore, overwrites errno. */ error = errno; if (t_errno == TSYSERR) { syslog(LOG_ERR, "%s(file descriptor %d/transport %s) %m", tli_name, fd, nconf->nc_proto); } else { syslog(LOG_ERR, "%s(file descriptor %d/transport %s) TLI error %d", tli_name, fd, nconf->nc_proto, t_errno); } errno = error; } /* * Called to set up service over a particular transport. */ void do_one(char *provider, NETSELDECL(proto), struct protob *protobp0, int (*svc)(int, struct netbuf, struct netconfig *)) { register int sock; struct protob *protobp; struct netbuf *retaddr; struct netconfig *retnconf; struct netbuf addrmask; int vers; int err; int l; if (provider) sock = bind_to_provider(provider, protobp0->serv, &retaddr, &retnconf); else sock = bind_to_proto(proto, protobp0->serv, &retaddr, &retnconf); if (sock == -1) { (void) syslog(LOG_ERR, "Cannot establish %s service over %s: transport setup problem.", protobp0->serv, provider ? provider : proto); return; } if (set_addrmask(sock, retnconf, &addrmask) < 0) { (void) syslog(LOG_ERR, "Cannot set address mask for %s", retnconf->nc_netid); return; } /* * Register all versions of the programs in the protocol block list. */ l = strlen(NC_UDP); for (protobp = protobp0; protobp; protobp = protobp->next) { for (vers = protobp->versmin; vers <= protobp->versmax; vers++) { if ((protobp->program == NFS_PROGRAM || protobp->program == NFS_ACL_PROGRAM) && vers == NFS_V4 && strncasecmp(retnconf->nc_proto, NC_UDP, l) == 0) continue; (void) rpcb_unset(protobp->program, vers, retnconf); (void) rpcb_set(protobp->program, vers, retnconf, retaddr); } } /* * Register services with CLTS semantics right now. * Note: services with COTS/COTS_ORD semantics will be * registered later from cots_listen_event function. */ if (retnconf->nc_semantics == NC_TPI_CLTS) { /* Don't drop core if supporting module(s) aren't loaded. */ (void) signal(SIGSYS, SIG_IGN); /* * svc() doesn't block, it returns success or failure. */ if (svc == NULL && Mysvc4 != NULL) err = (*Mysvc4)(sock, &addrmask, retnconf, NFS4_SETPORT|NFS4_KRPC_START, retaddr); else err = (*svc)(sock, addrmask, retnconf); if (err < 0) { (void) syslog(LOG_ERR, "Cannot establish %s service over : %m. Exiting", protobp0->serv, sock, retnconf->nc_proto); exit(1); } } free(addrmask.buf); /* * We successfully set up the server over this transport. * Add this descriptor to the one being polled on. */ add_to_poll_list(sock, retnconf); } /* * Set up the NFS service over all the available transports. * Returns -1 for failure, 0 for success. */ int do_all(struct protob *protobp, int (*svc)(int, struct netbuf, struct netconfig *)) { struct netconfig *nconf; NCONF_HANDLE *nc; int l; if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } l = strlen(NC_UDP); while (nconf = getnetconfig(nc)) { if ((nconf->nc_flag & NC_VISIBLE) && strcmp(nconf->nc_protofmly, NC_LOOPBACK) != 0 && OK_TPI_TYPE(nconf) && (protobp->program != NFS4_CALLBACK || strncasecmp(nconf->nc_proto, NC_UDP, l) != 0)) do_one(nconf->nc_device, nconf->nc_proto, protobp, svc); } (void) endnetconfig(nc); return (0); } /* * poll on the open transport descriptors for events and errors. */ void poll_for_action(void) { int nfds; int i; /* * Keep polling until all transports have been closed. When this * happens, we return. */ while ((int)num_fds > 0) { nfds = poll(poll_array, num_fds, INFTIM); switch (nfds) { case 0: continue; case -1: /* * Some errors from poll could be * due to temporary conditions, and we try to * be robust in the face of them. Other * errors (should never happen in theory) * are fatal (eg. EINVAL, EFAULT). */ switch (errno) { case EINTR: continue; case EAGAIN: case ENOMEM: (void) sleep(10); continue; default: (void) syslog(LOG_ERR, "poll failed: %m. Exiting"); exit(1); } default: break; } /* * Go through the poll list looking for events. */ for (i = 0; i < num_fds && nfds > 0; i++) { if (poll_array[i].revents) { nfds--; /* * We have a message, so try to read it. * Record the error return in errno, * so that syslog(LOG_ERR, "...%m") * dumps the corresponding error string. */ if (conn_polled[i].nc.nc_semantics == NC_TPI_CLTS) { errno = do_poll_clts_action( poll_array[i].fd, i); } else { errno = do_poll_cots_action( poll_array[i].fd, i); } if (errno == 0) continue; /* * Most returned error codes mean that there is * fatal condition which we can only deal with * by closing the transport. */ if (errno != EAGAIN && errno != ENOMEM) { (void) syslog(LOG_ERR, "Error (%m) reading descriptor %d/transport %s. Closing it.", poll_array[i].fd, conn_polled[i].nc.nc_proto); (void) t_close(poll_array[i].fd); remove_from_poll_list(poll_array[i].fd); } else if (errno == ENOMEM) (void) sleep(5); } } } (void) syslog(LOG_ERR, "All transports have been closed with errors. Exiting."); } /* * Allocate poll/transport array entries for this descriptor. */ static void add_to_poll_list(int fd, struct netconfig *nconf) { static int poll_array_size = 0; /* * If the arrays are full, allocate new ones. */ if (num_fds == poll_array_size) { struct pollfd *tpa; struct conn_entry *tnp; if (poll_array_size != 0) { tpa = poll_array; tnp = conn_polled; } else tpa = (struct pollfd *)0; poll_array_size += POLL_ARRAY_INC_SIZE; /* * Allocate new arrays. */ poll_array = (struct pollfd *) malloc(poll_array_size * sizeof (struct pollfd) + 256); conn_polled = (struct conn_entry *) malloc(poll_array_size * sizeof (struct conn_entry) + 256); if (poll_array == (struct pollfd *)NULL || conn_polled == (struct conn_entry *)NULL) { syslog(LOG_ERR, "malloc failed for poll array"); exit(1); } /* * Copy the data of the old ones into new arrays, and * free the old ones. */ if (tpa) { (void) memcpy((void *)poll_array, (void *)tpa, num_fds * sizeof (struct pollfd)); (void) memcpy((void *)conn_polled, (void *)tnp, num_fds * sizeof (struct conn_entry)); free((void *)tpa); free((void *)tnp); } } /* * Set the descriptor and event list. All possible events are * polled for. */ poll_array[num_fds].fd = fd; poll_array[num_fds].events = POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI; /* * Copy the transport data over too. */ conn_polled[num_fds].nc = *nconf; conn_polled[num_fds].closing = 0; /* * Set the descriptor to non-blocking. Avoids a race * between data arriving on the stream and then having it * flushed before we can read it. */ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { (void) syslog(LOG_ERR, "fcntl(file desc. %d/transport %s, F_SETFL, O_NONBLOCK): %m. Exiting", num_fds, nconf->nc_proto); exit(1); } /* * Count this descriptor. */ ++num_fds; } static void remove_from_poll_list(int fd) { int i; int num_to_copy; for (i = 0; i < num_fds; i++) { if (poll_array[i].fd == fd) { --num_fds; num_to_copy = num_fds - i; (void) memcpy((void *)&poll_array[i], (void *)&poll_array[i+1], num_to_copy * sizeof (struct pollfd)); (void) memset((void *)&poll_array[num_fds], 0, sizeof (struct pollfd)); (void) memcpy((void *)&conn_polled[i], (void *)&conn_polled[i+1], num_to_copy * sizeof (struct conn_entry)); (void) memset((void *)&conn_polled[num_fds], 0, sizeof (struct conn_entry)); return; } } syslog(LOG_ERR, "attempt to remove nonexistent fd from poll list"); } /* * Called to read and interpret the event on a connectionless descriptor. * Returns 0 if successful, or a UNIX error code if failure. */ static int do_poll_clts_action(int fd, int conn_index) { int error; int ret; int flags; struct netconfig *nconf = &conn_polled[conn_index].nc; static struct t_unitdata *unitdata = NULL; static struct t_uderr *uderr = NULL; static int oldfd = -1; struct nd_hostservlist *host = NULL; struct strbuf ctl[1], data[1]; /* * We just need to have some space to consume the * message in the event we can't use the TLI interface to do the * job. * * We flush the message using getmsg(). For the control part * we allocate enough for any TPI header plus 32 bytes for address * and options. For the data part, there is nothing magic about * the size of the array, but 256 bytes is probably better than * 1 byte, and we don't expect any data portion anyway. * * If the array sizes are too small, we handle this because getmsg() * (called to consume the message) will return MOREDATA|MORECTL. * Thus we just call getmsg() until it's read the message. */ char ctlbuf[sizeof (union T_primitives) + 32]; char databuf[256]; /* * If this is the same descriptor as the last time * do_poll_clts_action was called, we can save some * de-allocation and allocation. */ if (oldfd != fd) { oldfd = fd; if (unitdata) { (void) t_free((char *)unitdata, T_UNITDATA); unitdata = NULL; } if (uderr) { (void) t_free((char *)uderr, T_UDERROR); uderr = NULL; } } /* * Allocate a unitdata structure for receiving the event. */ if (unitdata == NULL) { /* LINTED pointer alignment */ unitdata = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ALL); if (unitdata == NULL) { if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UNITDATA) failed: %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UNITDATA) failed TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } } try_again: flags = 0; /* * The idea is we wait for T_UNITDATA_IND's. Of course, * we don't get any, because rpcmod filters them out. * However, we need to call t_rcvudata() to let TLI * tell us we have a T_UDERROR_IND. * * algorithm is: * t_rcvudata(), expecting TLOOK. * t_look(), expecting T_UDERR. * t_rcvuderr(), expecting success (0). * expand destination address into ASCII, * and dump it. */ ret = t_rcvudata(fd, unitdata, &flags); if (ret == 0 || t_errno == TBUFOVFLW) { (void) syslog(LOG_WARNING, "t_rcvudata(file descriptor %d/transport %s) got unexpected data, %d bytes", fd, nconf->nc_proto, unitdata->udata.len); /* * Even though we don't expect any data, in case we do, * keep reading until there is no more. */ if (flags & T_MORE) goto try_again; return (0); } switch (t_errno) { case TNODATA: return (0); case TSYSERR: /* * System errors are returned to caller. * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_rcvudata(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); case TLOOK: break; default: (void) syslog(LOG_ERR, "t_rcvudata(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } ret = t_look(fd); switch (ret) { case 0: return (0); case -1: /* * System errors are returned to caller. */ if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_look(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_look(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; case T_UDERR: break; default: (void) syslog(LOG_WARNING, "t_look(file descriptor %d/transport %s) returned %d not T_UDERR (%d)", fd, nconf->nc_proto, ret, T_UDERR); } if (uderr == NULL) { /* LINTED pointer alignment */ uderr = (struct t_uderr *)t_alloc(fd, T_UDERROR, T_ALL); if (uderr == NULL) { if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UDERROR) failed: %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, T_UDERROR) failed TLI error: %d", fd, nconf->nc_proto, t_errno); goto flush_it; } } ret = t_rcvuderr(fd, uderr); if (ret == 0) { /* * Save the datagram error in errno, so that the * %m argument to syslog picks up the error string. */ errno = uderr->error; /* * Log the datagram error, then log the host that * probably triggerred. Cannot log both in the * same transaction because of packet size limitations * in /dev/log. */ (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "NFS response over generated error: %m", fd, nconf->nc_proto); /* * Try to map the client's address back to a * name. */ ret = netdir_getbyaddr(nconf, &host, &uderr->addr); if (ret != -1 && host && host->h_cnt > 0 && host->h_hostservs) { (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "Bad NFS response was sent to client with host name: %s; service port: %s", host->h_hostservs->h_host, host->h_hostservs->h_serv); } else { int i, j; char *buf; char *hex = "0123456789abcdef"; /* * Mapping failed, print the whole thing * in ASCII hex. */ buf = (char *)malloc(uderr->addr.len * 2 + 1); for (i = 0, j = 0; i < uderr->addr.len; i++, j += 2) { buf[j] = hex[((uderr->addr.buf[i]) >> 4) & 0xf]; buf[j+1] = hex[uderr->addr.buf[i] & 0xf]; } buf[j] = '\0'; (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "Bad NFS response was sent to client with transport address: 0x%s", buf); free((void *)buf); } if (ret == 0 && host != NULL) netdir_free((void *)host, ND_HOSTSERVLIST); return (0); } switch (t_errno) { case TNOUDERR: goto flush_it; case TSYSERR: /* * System errors are returned to caller. * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_rcvuderr(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); default: (void) syslog(LOG_ERR, "t_rcvuderr(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } flush_it: /* * If we get here, then we could not cope with whatever message * we attempted to read, so flush it. If we did read a message, * and one isn't present, that is all right, because fd is in * nonblocking mode. */ (void) syslog(LOG_ERR, "Flushing one input message from ", fd, nconf->nc_proto); /* * Read and discard the message. Do this this until there is * no more control/data in the message or until we get an error. */ do { ctl->maxlen = sizeof (ctlbuf); ctl->buf = ctlbuf; data->maxlen = sizeof (databuf); data->buf = databuf; flags = 0; ret = getmsg(fd, ctl, data, &flags); if (ret == -1) return (errno); } while (ret != 0); return (0); } static void conn_close_oldest(void) { int fd; int i1; /* * Find the oldest connection that is not already in the * process of shutting down. */ for (i1 = end_listen_fds; /* no conditional expression */; i1++) { if (i1 >= num_fds) return; if (conn_polled[i1].closing == 0) break; } #ifdef DEBUG printf("too many connections (%d), releasing oldest (%d)\n", num_conns, poll_array[i1].fd); #else syslog(LOG_WARNING, "too many connections (%d), releasing oldest (%d)", num_conns, poll_array[i1].fd); #endif fd = poll_array[i1].fd; if (conn_polled[i1].nc.nc_semantics == NC_TPI_COTS) { /* * For politeness, send a T_DISCON_REQ to the transport * provider. We close the stream anyway. */ (void) t_snddis(fd, (struct t_call *)0); num_conns--; remove_from_poll_list(fd); (void) t_close(fd); } else { /* * For orderly release, we do not close the stream * until the T_ORDREL_IND arrives to complete * the handshake. */ if (t_sndrel(fd) == 0) conn_polled[i1].closing = 1; } } static boolean_t conn_get(int fd, struct netconfig *nconf, struct conn_ind **connp) { struct conn_ind *conn; struct conn_ind *next_conn; conn = (struct conn_ind *)malloc(sizeof (*conn)); if (conn == NULL) { syslog(LOG_ERR, "malloc for listen indication failed"); return (FALSE); } /* LINTED pointer alignment */ conn->conn_call = (struct t_call *)t_alloc(fd, T_CALL, T_ALL); if (conn->conn_call == NULL) { free((char *)conn); nfslib_log_tli_error("t_alloc", fd, nconf); return (FALSE); } if (t_listen(fd, conn->conn_call) == -1) { nfslib_log_tli_error("t_listen", fd, nconf); (void) t_free((char *)conn->conn_call, T_CALL); free((char *)conn); return (FALSE); } if (conn->conn_call->udata.len > 0) { syslog(LOG_WARNING, "rejecting inbound connection(%s) with %d bytes of connect data", nconf->nc_proto, conn->conn_call->udata.len); conn->conn_call->udata.len = 0; (void) t_snddis(fd, conn->conn_call); (void) t_free((char *)conn->conn_call, T_CALL); free((char *)conn); return (FALSE); } if ((next_conn = *connp) != NULL) { next_conn->conn_prev->conn_next = conn; conn->conn_next = next_conn; conn->conn_prev = next_conn->conn_prev; next_conn->conn_prev = conn; } else { conn->conn_next = conn; conn->conn_prev = conn; *connp = conn; } return (TRUE); } static int discon_get(int fd, struct netconfig *nconf, struct conn_ind **connp) { struct conn_ind *conn; struct t_discon discon; discon.udata.buf = (char *)0; discon.udata.maxlen = 0; if (t_rcvdis(fd, &discon) == -1) { nfslib_log_tli_error("t_rcvdis", fd, nconf); return (-1); } conn = *connp; if (conn == NULL) return (0); do { if (conn->conn_call->sequence == discon.sequence) { if (conn->conn_next == conn) *connp = (struct conn_ind *)0; else { if (conn == *connp) { *connp = conn->conn_next; } conn->conn_next->conn_prev = conn->conn_prev; conn->conn_prev->conn_next = conn->conn_next; } free((char *)conn); break; } conn = conn->conn_next; } while (conn != *connp); return (0); } static void cots_listen_event(int fd, int conn_index) { struct t_call *call; struct conn_ind *conn; struct conn_ind *conn_head; int event; struct netconfig *nconf = &conn_polled[conn_index].nc; int new_fd; struct netbuf addrmask; int ret = 0; char *clnt; char *clnt_uaddr = NULL; struct nd_hostservlist *clnt_serv = NULL; conn_head = NULL; (void) conn_get(fd, nconf, &conn_head); while ((conn = conn_head) != NULL) { conn_head = conn->conn_next; if (conn_head == conn) conn_head = NULL; else { conn_head->conn_prev = conn->conn_prev; conn->conn_prev->conn_next = conn_head; } call = conn->conn_call; free(conn); /* * If we have already accepted the maximum number of * connections allowed on the command line, then drop * the oldest connection (for any protocol) before * accepting the new connection. Unless explicitly * set on the command line, max_conns_allowed is -1. */ if (max_conns_allowed != -1 && num_conns >= max_conns_allowed) conn_close_oldest(); /* * Create a new transport endpoint for the same proto as * the listener. */ new_fd = nfslib_transport_open(nconf); if (new_fd == -1) { call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); syslog(LOG_ERR, "Cannot establish transport over %s", nconf->nc_device); continue; } /* Bind to a generic address/port for the accepting stream. */ if (t_bind(new_fd, NULL, NULL) == -1) { nfslib_log_tli_error("t_bind", new_fd, nconf); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); continue; } while (t_accept(fd, new_fd, call) == -1) { if (t_errno != TLOOK) { #ifdef DEBUG nfslib_log_tli_error("t_accept", fd, nconf); #endif call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } while (event = t_look(fd)) { switch (event) { case T_LISTEN: #ifdef DEBUG printf( "cots_listen_event(%s): T_LISTEN during accept processing\n", nconf->nc_proto); #endif (void) conn_get(fd, nconf, &conn_head); continue; case T_DISCONNECT: #ifdef DEBUG printf( "cots_listen_event(%s): T_DISCONNECT during accept processing\n", nconf->nc_proto); #endif (void) discon_get(fd, nconf, &conn_head); continue; default: syslog(LOG_ERR, "unexpected event 0x%x during accept processing (%s)", event, nconf->nc_proto); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } } } if (set_addrmask(new_fd, nconf, &addrmask) < 0) { (void) syslog(LOG_ERR, "Cannot set address mask for %s", nconf->nc_netid); (void) t_snddis(new_fd, NULL); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); continue; } /* Tell kRPC about the new stream. */ if (Mysvc4 != NULL) ret = (*Mysvc4)(new_fd, &addrmask, nconf, NFS4_KRPC_START, &call->addr); else ret = (*Mysvc)(new_fd, addrmask, nconf); if (ret < 0) { if (errno != ENOTCONN) { syslog(LOG_ERR, "unable to register new connection: %m"); } else { /* * This is the only error that could be * caused by the client, so who was it? */ if (netdir_getbyaddr(nconf, &clnt_serv, &(call->addr)) == ND_OK && clnt_serv->h_cnt > 0) clnt = clnt_serv->h_hostservs->h_host; else clnt = clnt_uaddr = taddr2uaddr(nconf, &(call->addr)); /* * If we don't know who the client was, * remain silent. */ if (clnt) syslog(LOG_ERR, "unable to register new connection: client %s has dropped connection", clnt); if (clnt_serv) { netdir_free(clnt_serv, ND_HOSTSERVLIST); clnt_serv = NULL; } if (clnt_uaddr) { free(clnt_uaddr); clnt_uaddr = NULL; } } free(addrmask.buf); (void) t_snddis(new_fd, NULL); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } free(addrmask.buf); (void) t_free((char *)call, T_CALL); /* * Poll on the new descriptor so that we get disconnect * and orderly release indications. */ num_conns++; add_to_poll_list(new_fd, nconf); /* Reset nconf in case it has been moved. */ nconf = &conn_polled[conn_index].nc; do_next_conn:; } } static int do_poll_cots_action(int fd, int conn_index) { char buf[256]; int event; int i1; int flags; struct conn_entry *connent = &conn_polled[conn_index]; struct netconfig *nconf = &(connent->nc); const char *errorstr; while (event = t_look(fd)) { switch (event) { case T_LISTEN: #ifdef DEBUG printf("do_poll_cots_action(%s,%d): T_LISTEN event\n", nconf->nc_proto, fd); #endif cots_listen_event(fd, conn_index); break; case T_DATA: #ifdef DEBUG printf("do_poll_cots_action(%d,%s): T_DATA event\n", fd, nconf->nc_proto); #endif /* * Receive a private notification from CONS rpcmod. */ i1 = t_rcv(fd, buf, sizeof (buf), &flags); if (i1 == -1) { syslog(LOG_ERR, "t_rcv failed"); break; } if (i1 < sizeof (int)) break; i1 = BE32_TO_U32(buf); if (i1 == 1 || i1 == 2) { /* * This connection has been idle for too long, * so release it as politely as we can. If we * have already initiated an orderly release * and we get notified that the stream is * still idle, pull the plug. This prevents * hung connections from continuing to consume * resources. */ #ifdef DEBUG printf("do_poll_cots_action(%s,%d): ", nconf->nc_proto, fd); printf("initiating orderly release of idle connection\n"); #endif if (nconf->nc_semantics == NC_TPI_COTS || connent->closing != 0) { (void) t_snddis(fd, (struct t_call *)0); goto fdclose; } /* * For NC_TPI_COTS_ORD, the stream is closed * and removed from the poll list when the * T_ORDREL is received from the provider. We * don't wait for it here because it may take * a while for the transport to shut down. */ if (t_sndrel(fd) == -1) { syslog(LOG_ERR, "unable to send orderly release %m"); } connent->closing = 1; } else syslog(LOG_ERR, "unexpected event from CONS rpcmod %d", i1); break; case T_ORDREL: #ifdef DEBUG printf("do_poll_cots_action(%s,%d): T_ORDREL event\n", nconf->nc_proto, fd); #endif /* Perform an orderly release. */ if (t_rcvrel(fd) == 0) { /* T_ORDREL on listen fd's should be ignored */ if (!is_listen_fd_index(conn_index)) { (void) t_sndrel(fd); goto fdclose; } break; } else if (t_errno == TLOOK) { break; } else { nfslib_log_tli_error("t_rcvrel", fd, nconf); /* * check to make sure we do not close * listen fd */ if (is_listen_fd_index(conn_index)) break; else goto fdclose; } case T_DISCONNECT: #ifdef DEBUG printf("do_poll_cots_action(%s,%d): T_DISCONNECT event\n", nconf->nc_proto, fd); #endif if (t_rcvdis(fd, (struct t_discon *)NULL) == -1) nfslib_log_tli_error("t_rcvdis", fd, nconf); /* * T_DISCONNECT on listen fd's should be ignored. */ if (is_listen_fd_index(conn_index)) break; else goto fdclose; default: if (t_errno == TSYSERR) { if ((errorstr = strerror(errno)) == NULL) { (void) sprintf(buf, "Unknown error num %d", errno); errorstr = (const char *) buf; } } else if (event == -1) errorstr = t_strerror(t_errno); else errorstr = ""; syslog(LOG_ERR, "unexpected TLI event (0x%x) on " "connection-oriented transport(%s,%d):%s", event, nconf->nc_proto, fd, errorstr); fdclose: num_conns--; remove_from_poll_list(fd); (void) t_close(fd); return (0); } } return (0); } static char * serv_name_to_port_name(char *name) { /* * Map service names (used primarily in logging) to * RPC port names (used by netdir_*() routines). */ if (strcmp(name, "NFS") == 0) { return ("nfs"); } else if (strcmp(name, "NLM") == 0) { return ("lockd"); } else if (strcmp(name, "NFS4_CALLBACK") == 0) { return ("nfs4_callback"); } return ("unrecognized"); } static int bind_to_provider(char *provider, char *serv, struct netbuf **addr, struct netconfig **retnconf) { struct netconfig *nconf; NCONF_HANDLE *nc; struct nd_hostserv hs; hs.h_host = HOST_SELF; hs.h_serv = serv_name_to_port_name(serv); if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while (nconf = getnetconfig(nc)) { if (OK_TPI_TYPE(nconf) && strcmp(nconf->nc_device, provider) == 0) { *retnconf = nconf; return (nfslib_bindit(nconf, addr, &hs, listen_backlog)); } } (void) endnetconfig(nc); syslog(LOG_ERR, "couldn't find netconfig entry for provider %s", provider); return (-1); } static int bind_to_proto(NETSELDECL(proto), char *serv, struct netbuf **addr, struct netconfig **retnconf) { struct netconfig *nconf; NCONF_HANDLE *nc = NULL; struct nd_hostserv hs; hs.h_host = HOST_SELF; hs.h_serv = serv_name_to_port_name(serv); if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while (nconf = getnetconfig(nc)) { if (OK_TPI_TYPE(nconf) && NETSELEQ(nconf->nc_proto, proto)) { *retnconf = nconf; return (nfslib_bindit(nconf, addr, &hs, listen_backlog)); } } (void) endnetconfig(nc); syslog(LOG_ERR, "couldn't find netconfig entry for protocol %s", proto); return (-1); } #include /* * Create an address mask appropriate for the transport. * The mask is used to obtain the host-specific part of * a network address when comparing addresses. * For an internet address the host-specific part is just * the 32 bit IP address and this part of the mask is set * to all-ones. The port number part of the mask is zeroes. */ static int set_addrmask(int fd, struct netconfig *nconf, struct netbuf *mask) { struct t_info info; /* * Find the size of the address we need to mask. */ if (t_getinfo(fd, &info) < 0) { t_error("t_getinfo"); return (-1); } mask->len = mask->maxlen = info.addr; if (info.addr <= 0) { /* * loopback devices have infinite addr size * (it is identified by -1 in addr field of t_info structure), * so don't build the netmask for them. It's a special case * that should be handled properly. */ if ((info.addr == -1) && (0 == strcmp(nconf->nc_protofmly, NC_LOOPBACK))) { memset(mask, 0, sizeof (*mask)); return (0); } syslog(LOG_ERR, "set_addrmask: address size: %ld", info.addr); return (-1); } mask->buf = (char *)malloc(mask->len); if (mask->buf == NULL) { syslog(LOG_ERR, "set_addrmask: no memory"); return (-1); } (void) memset(mask->buf, 0, mask->len); /* reset all mask bits */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { /* * Set the mask so that the port is ignored. */ /* LINTED pointer alignment */ ((struct sockaddr_in *)mask->buf)->sin_addr.s_addr = (in_addr_t)~0; /* LINTED pointer alignment */ ((struct sockaddr_in *)mask->buf)->sin_family = (ushort_t)~0; } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { /* LINTED pointer alignment */ (void) memset(&((struct sockaddr_in6 *)mask->buf)->sin6_addr, (uchar_t)~0, sizeof (struct in6_addr)); /* LINTED pointer alignment */ ((struct sockaddr_in6 *)mask->buf)->sin6_family = (ushort_t)~0; } else { /* * Set all mask bits. */ (void) memset(mask->buf, 0xFF, mask->len); } return (0); } /* * For listen fd's index is always less than end_listen_fds. * end_listen_fds is defined externally in the daemon that uses this library. * It's value is equal to the number of open file descriptors after the * last listen end point was opened but before any connection was accepted. */ static int is_listen_fd_index(int index) { return (index < end_listen_fds); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * nfs_tbind.h, common code for nfsd and lockd */ #ifndef _NFS_TBIND_H #define _NFS_TBIND_H #include #include #ifdef __cplusplus extern "C" { #endif /* * Globals which should be initialised by daemon main(). */ extern size_t end_listen_fds; extern size_t num_fds; extern int listen_backlog; extern int (*Mysvc)(int, struct netbuf, struct netconfig *); extern int (*Mysvc4)(int, struct netbuf *, struct netconfig *, int, struct netbuf *); extern int max_conns_allowed; /* * RPC protocol block. Useful for passing registration information. */ struct protob { char *serv; /* ASCII service name, e.g. "NFS" */ int versmin; /* minimum version no. to be registered */ int versmax; /* maximum version no. to be registered */ int program; /* program no. to be registered */ struct protob *next; /* next entry on list */ }; /* * Declarations for protocol types and comparison. */ #define NETSELDECL(x) char *x #define NETSELPDECL(x) char **x #define NETSELEQ(x, y) (strcmp((x), (y)) == 0) /* * nfs library routines */ extern int nfslib_transport_open(struct netconfig *); extern int nfslib_bindit(struct netconfig *, struct netbuf **, struct nd_hostserv *, int); extern void nfslib_log_tli_error(char *, int, struct netconfig *); extern int do_all(struct protob *, int (*)(int, struct netbuf, struct netconfig *)); extern void do_one(char *, char *, struct protob *, int (*)(int, struct netbuf, struct netconfig *)); extern void poll_for_action(void); #ifdef __cplusplus } #endif #endif /* _NFS_TBIND_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nfslog_config.h" #define ERROR_BUFSZ 100 /* * This flag controls where error messages go. * Zero means that messages go to stderr. * Non-zero means that messages go to syslog. */ boolean_t nfsl_errs_to_syslog; /* * Pointer to the global entry in the list */ static nfsl_config_t *global = NULL; /* * Pointer to the raw global entry in the list, this is the * global entry without the expanded paths. This is used to * complete configurations. */ static nfsl_config_t *global_raw = NULL; /* * Last modification time to config file. */ static timestruc_t config_last_modification = { 0 }; /* * Whitespace characters to delimit fields in a line. */ static const char *whitespace = " \t"; static int getconfiglist(nfsl_config_t **, boolean_t); static nfsl_config_t *create_config(char *, char *, char *, char *, char *, char *, int, boolean_t, int *); static nfsl_config_t *create_global_raw(int *); static int update_config(nfsl_config_t *, char *, char *, char *, char *, char *, char *, int, boolean_t, boolean_t); static int update_field(char **, char *, char *, boolean_t *); static nfsl_config_t *findconfig(nfsl_config_t **, char *, boolean_t, nfsl_config_t **); static nfsl_config_t *getlastconfig(nfsl_config_t *); static void complete_with_global(char **, char **, char **, char **, char **, int *); #ifdef DEBUG static void remove_config(nfsl_config_t **, nfsl_config_t *, nfsl_config_t **); void nfsl_printconfig(nfsl_config_t *); #endif /* DEBUG */ static char *gataline(FILE *, char *, char *, int); static int get_info(char *, char **, char **, char **, char **, char **, char **, int *); static void free_config(nfsl_config_t *); static int is_legal_tag(char *); static boolean_t is_complete_config(char *, char *, char *, char *); /* * Read the configuration file and create a list of configuration * parameters. Returns zero for success or an errno value. * The caller is responsible for freeing the returned configlist by calling * nfsl_freeconfig_list(). * * If the configuration file does not exist, *listpp points to a config entry * containing the hardwired defaults. */ int nfsl_getconfig_list(nfsl_config_t **listpp) { int error = 0; char *locale; /* * Set the locale correctly so that we can correctly identify * alphabetic characters. */ if ((locale = getenv("LC_ALL")) != NULL) (void) setlocale(LC_ALL, locale); else if ((locale = getenv("LC_CTYPE")) != NULL) (void) setlocale(LC_CTYPE, locale); else if ((locale = getenv("LANG")) != NULL) (void) setlocale(LC_CTYPE, locale); /* * Allocate 'global_raw' structure, its contents are * indirectly allocated by create_config(). */ assert(global_raw == NULL); global_raw = create_global_raw(&error); if (global_raw == NULL) return (error); /* * Build global entry with hardwired defaults first. */ assert(global == NULL); global = create_config(DEFAULTTAG, DEFAULTDIR, BUFFERPATH, NULL, FHPATH, LOGPATH, TRANSLOG_BASIC, B_TRUE, &error); *listpp = global; if (global == NULL) { free_config(global_raw); return (error); } error = getconfiglist(listpp, B_FALSE); if (error != 0) { nfsl_freeconfig_list(listpp); } else { assert(global != NULL); /* * The global entry was replaced with the one in the file, * clear the UPDATED flag */ global->nc_flags &= ~NC_UPDATED; } return (error); } /* * Allocates memory for the 'global_raw' structure. * The actual allocation of values for its components happens in * update_config(). */ static nfsl_config_t * create_global_raw(int *error) { nfsl_config_t *p; *error = 0; p = calloc(1, sizeof (*p)); if (p == NULL) *error = ENOMEM; return (p); } /* * Checks if the the configuration file has been modified since we last * read it, if not simply returns, otherwise it re-reads it adding new * configuration entries. Note that existing entries that no longer * exist in the configuration file are not removed. Existing entries * that are modified in the configuration file are updated in the list * as well. * if 'updated' is defined then it is set to TRUE if the list was modified. * * Note that if an error occurs, the list may be corrupted. * It is the responsibility of the caller to free the list. * If the configuration file does not exist, we simply return the list * that we previously had, log a message and return success. */ int nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *updated) { struct stat st; int error = 0; if (updated != NULL) *updated = B_FALSE; if (stat(NFSL_CONFIG_FILE_PATH, &st) == -1) { error = errno; if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH, strerror(error)); } else { (void) fprintf(stderr, gettext( "Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH, strerror(error)); } return (0); } if (config_last_modification.tv_sec == st.st_mtim.tv_sec && config_last_modification.tv_nsec == st.st_mtim.tv_nsec) return (0); if (updated != NULL) *updated = B_TRUE; return (getconfiglist(listpp, B_TRUE)); } /* * Does the real work. Reads the configuration file and creates the * list of entries. Assumes that *listpp contains at least one entry. * The caller is responsible for freeing any config entries added to * the list whether this routine returns an error or not. * * Returns 0 on success and updates the '*listpp' config list, * Returns non-zero error value otherwise. */ static int getconfiglist(nfsl_config_t **listpp, boolean_t updating) { FILE *fp; int error = 0; nfsl_config_t *listp = NULL, *tail = NULL; char linebuf[MAX_LINESZ]; char errorbuf[ERROR_BUFSZ]; char *tag, *defaultdir, *bufferpath, *rpclogpath, *fhpath, *logpath; int logformat; flock_t flock; struct stat st; fp = fopen(NFSL_CONFIG_FILE_PATH, "r"); if (fp == NULL) { if (updating) { (void) sprintf(errorbuf, "Can't open %s", NFSL_CONFIG_FILE_PATH); } else { (void) sprintf(errorbuf, "Can't open %s - using hardwired defaults", NFSL_CONFIG_FILE_PATH); } /* * Use hardwired config. */ if (nfsl_errs_to_syslog) syslog(LOG_ERR, gettext("%s"), errorbuf); else (void) fprintf(stderr, gettext("%s\n"), errorbuf); return (0); } (void) memset((void *) &flock, 0, sizeof (flock)); flock.l_type = F_RDLCK; if (fcntl(fileno(fp), F_SETLKW, &flock) == -1) { error = errno; if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Can't lock %s - %s"), NFSL_CONFIG_FILE_PATH, strerror(error)); } else { (void) fprintf(stderr, gettext( "Can't lock %s - %s\n"), NFSL_CONFIG_FILE_PATH, strerror(error)); } goto done; } assert (*listpp != NULL); tail = getlastconfig(*listpp); while (gataline(fp, NFSL_CONFIG_FILE_PATH, linebuf, sizeof (linebuf))) { if (linebuf[0] == '\0') { /* * ignore lines that exceed max size */ continue; } error = get_info(linebuf, &tag, &defaultdir, &bufferpath, &rpclogpath, &fhpath, &logpath, &logformat); if (error != 0) break; listp = findconfig(listpp, tag, B_FALSE, &tail); if (listp != NULL) { /* * An entry with the same tag name exists, * update the fields that changed. */ error = update_config(listp, tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, B_TRUE, B_TRUE); if (error) break; } else { /* * New entry, create it. */ listp = create_config(tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, B_TRUE, &error); if (listp == NULL) break; if (*listpp == NULL) *listpp = listp; else tail->nc_next = listp; tail = listp; } assert(global != NULL); } if (error == 0) { /* * Get mtime while we have file locked */ error = fstat(fileno(fp), &st); if (error != 0) { error = errno; if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Can't stat %s - %s"), NFSL_CONFIG_FILE_PATH, strerror(error)); } else { (void) fprintf(stderr, gettext( "Can't stat %s - %s\n"), NFSL_CONFIG_FILE_PATH, strerror(error)); } } config_last_modification = st.st_mtim; } done: (void) fclose(fp); return (error); } /* * Creates the config structure with the values specified by the * parameters. If defaultdir has been specified, all relative paths * are prepended with this defaultdir. * If 'complete' is set then this must represent a complete config entry * as specified by is_complete_config(), otherwise no work is perfomed, and * NULL is returned. * * Returns the newly created config structure on success. * Returns NULL on failure and sets error to the appropriate error. */ static nfsl_config_t * create_config( char *tag, char *defaultdir, char *bufferpath, char *rpclogpath, char *fhpath, char *logpath, int logformat, boolean_t complete, int *error) { nfsl_config_t *config; config = calloc(1, sizeof (*config)); if (config == NULL) { *error = ENOMEM; return (NULL); } *error = update_config(config, tag, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, complete, B_TRUE); if (*error) { free(config); return (NULL); } config->nc_flags &= ~NC_UPDATED; /* This is a new entry */ return (config); } /* * Updates the configuration entry with the new information provided, * sets NC_UPDATED to indicate so. The entry is left untouched if all * the fields are the same (except for 'nc_rpccookie', 'nc_transcookie' * and 'nc_next'). * Prepends each path component with 'defauldir' if 'prepend' is set. * * Returns 0 on success, error otherwise. * On error, the config entry is left in an inconsistent state. * The only thing the caller can really do with it is free it. */ static int update_config( nfsl_config_t *config, char *tag, char *defaultdir, char *bufferpath, char *rpclogpath, char *fhpath, char *logpath, int logformat, boolean_t complete, boolean_t prepend) { boolean_t updated, config_updated = B_FALSE; int error = 0; if (complete && !is_complete_config(tag, bufferpath, fhpath, logpath)) { /* * Not a complete entry */ if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "update_config: \"%s\" not a complete " "config entry."), tag); } else { (void) fprintf(stderr, gettext( "update_config: \"%s\" not a complete " "config entry.\n"), tag); } return (EINVAL); } assert(tag != NULL); if (config->nc_name == NULL) { /* * New entry */ if ((config->nc_name = strdup(tag)) == NULL) { error = ENOMEM; goto errout; } } else { assert(strcmp(config->nc_name, tag) == 0); } error = update_field( &config->nc_defaultdir, defaultdir, NULL, &updated); if (error != 0) goto errout; if (!prepend) { /* * Do not prepend default directory. */ defaultdir = NULL; } config_updated |= updated; error = update_field( &config->nc_bufferpath, bufferpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; error = update_field( &config->nc_rpclogpath, rpclogpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; error = update_field( &config->nc_fhpath, fhpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; error = update_field( &config->nc_logpath, logpath, defaultdir, &updated); if (error != 0) goto errout; config_updated |= updated; updated = (config->nc_logformat != logformat); if (updated) config->nc_logformat = logformat; config_updated |= updated; if (config_updated) config->nc_flags |= NC_UPDATED; if (strcmp(tag, DEFAULTTAG) == 0) { /* * Have the default global config point to this entry. */ global = config; /* * Update the global_raw configuration entry. * Make sure no expanding of paths occurs. */ error = update_config(global_raw, DEFAULTRAWTAG, defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat, complete, B_FALSE); if (error != 0) goto errout; } return (error); errout: if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "update_config: Can't process \"%s\" config entry: %s"), tag, strerror(error)); } else { (void) fprintf(stderr, gettext( "update_config: Can't process \"%s\" config entry: %s\n"), tag, strerror(error)); } return (error); } /* * Prepends 'prependir' to 'new' if 'prependir' is defined. * Compares the value of '*old' with 'new', if it has changed, * then sets whatever 'old' references equal to 'new'. * Returns 0 on success, error otherwise. * Sets '*updated' to B_TRUE if field was modified. * The value of '*updated' is undefined on error. */ static int update_field( char **old, /* pointer to config field */ char *new, /* updated value */ char *prependdir, /* prepend this directory to new */ boolean_t *updated) /* field was modified */ { char *tmp_new = NULL; int need_update = 0; if (new != NULL) { if (prependdir != NULL && new[0] != '/') { tmp_new = malloc(strlen(prependdir) + strlen(new) + 2); if (tmp_new == NULL) return (ENOMEM); (void) sprintf(tmp_new, "%s/%s", prependdir, new); } else { if ((tmp_new = strdup(new)) == NULL) return (ENOMEM); } } if (tmp_new != NULL) { if (*old == NULL) need_update++; else if (strcmp(tmp_new, *old) != 0) { free(*old); need_update++; } if (need_update) *old = tmp_new; } else if (*old != NULL) { need_update++; free(*old); *old = NULL; } *updated = need_update != 0; return (0); } #ifdef DEBUG /* * Removes and frees the 'config' entry from the list * pointed to by '*listpp'. * No error is reported if the entry does not exist. * Updates '*tail' to point to the last item in the list. */ static void remove_config( nfsl_config_t **listpp, nfsl_config_t *config, nfsl_config_t **tail) { nfsl_config_t *p, *prev; prev = *listpp; for (p = *listpp; p != NULL; p = p->nc_next) { if (p == config) { if (p == prev) { /* * first element of the list */ *listpp = prev->nc_next; } else prev->nc_next = p->nc_next; free_config(p); break; } prev = p; } /* * Find tail of the list. */ for (*tail = prev; (*tail)->nc_next != NULL; *tail = (*tail)->nc_next) ; } #endif /* DEBUG */ static void free_config(nfsl_config_t *config) { if (config == NULL) return; if (config->nc_name) free(config->nc_name); if (config->nc_defaultdir) free(config->nc_defaultdir); if (config->nc_bufferpath) free(config->nc_bufferpath); if (config->nc_rpclogpath) free(config->nc_rpclogpath); if (config->nc_fhpath) free(config->nc_fhpath); if (config->nc_logpath) free(config->nc_logpath); if (config == global) global = NULL; if (config == global_raw) global_raw = NULL; free(config); } void nfsl_freeconfig_list(nfsl_config_t **listpp) { nfsl_config_t *next; if (*listpp == NULL) return; do { next = (*listpp)->nc_next; free_config(*listpp); *listpp = next; } while (*listpp); free_config(global_raw); } /* * Returns a pointer to the first instance of 'tag' in the list. * If 'remove' is true, then the entry is removed from the list and * a pointer to it is returned. * If '*tail' is not NULL, then it will point to the last element of * the list. Note that this function assumes that *tail already * points at the last element of the list. * Returns NULL if the entry does not exist. */ static nfsl_config_t * findconfig( nfsl_config_t **listpp, char *tag, boolean_t remove, nfsl_config_t **tail) { nfsl_config_t *p, *prev; prev = *listpp; for (p = *listpp; p != NULL; p = p->nc_next) { if (strcmp(p->nc_name, tag) == 0) { if (remove) { if (p == prev) { /* * first element of the list */ *listpp = prev->nc_next; } else prev->nc_next = p->nc_next; if (tail != NULL && p == *tail) { /* * Only update *tail if we removed * the last element of the list, and we * requested *tail to be updated. */ *tail = prev; } } return (p); } prev = p; } return (NULL); } static nfsl_config_t * getlastconfig(nfsl_config_t *listp) { nfsl_config_t *lastp = NULL; for (; listp != NULL; listp = listp->nc_next) lastp = listp; return (lastp); } /* * Returns a pointer to the first instance of 'tag' in the list. * Returns NULL if the entry does not exist. * Sets 'error' if the update of the list failed if necessary, and * returns NULL. */ nfsl_config_t * nfsl_findconfig(nfsl_config_t *listp, char *tag, int *error) { nfsl_config_t *config; boolean_t updated; *error = 0; config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL); if (config == NULL) { /* * Rebuild our list if the file has changed. */ *error = nfsl_checkconfig_list(&listp, &updated); if (*error != 0) { /* * List may be corrupted, notify caller. */ return (NULL); } if (updated) { /* * Search for tag again. */ config = findconfig(&listp, tag, B_FALSE, (nfsl_config_t **)NULL); } } return (config); } /* * Use the raw global values if any of the parameters is not defined. */ static void complete_with_global( char **defaultdir, char **bufferpath, char **rpclogpath, char **fhpath, char **logpath, int *logformat) { if (*defaultdir == NULL) *defaultdir = global_raw->nc_defaultdir; if (*bufferpath == NULL) *bufferpath = global_raw->nc_bufferpath; if (*rpclogpath == NULL) *rpclogpath = global_raw->nc_rpclogpath; if (*fhpath == NULL) *fhpath = global_raw->nc_fhpath; if (*logpath == NULL) *logpath = global_raw->nc_logpath; if (*logformat == 0) *logformat = global_raw->nc_logformat; } /* * Parses 'linebuf'. Returns 0 if a valid tag is found, otherwise non-zero. * Unknown tokens are silently ignored. * It is the responsibility of the caller to make a copy of the non-NULL * parameters if they need to be used before linebuf is freed. */ static int get_info( char *linebuf, char **tag, char **defaultdir, char **bufferpath, char **rpclogpath, char **fhpath, char **logpath, int *logformat) { char *tok; char *tmp; /* tag */ *tag = NULL; tok = strtok(linebuf, whitespace); if (tok == NULL) goto badtag; if (!is_legal_tag(tok)) goto badtag; *tag = tok; *defaultdir = *bufferpath = *rpclogpath = NULL; *fhpath = *logpath = NULL; *logformat = 0; while ((tok = strtok(NULL, whitespace)) != NULL) { if (strncmp(tok, "defaultdir=", strlen("defaultdir=")) == 0) { *defaultdir = tok + strlen("defaultdir="); } else if (strncmp(tok, "buffer=", strlen("buffer=")) == 0) { *bufferpath = tok + strlen("buffer="); } else if (strncmp(tok, "rpclog=", strlen("rpclog=")) == 0) { *rpclogpath = tok + strlen("rpclog="); } else if (strncmp(tok, "fhtable=", strlen("fhtable=")) == 0) { *fhpath = tok + strlen("fhtable="); } else if (strncmp(tok, "log=", strlen("log=")) == 0) { *logpath = tok + strlen("log="); } else if (strncmp(tok, "logformat=", strlen("logformat=")) == 0) { tmp = tok + strlen("logformat="); if (strncmp(tmp, "extended", strlen("extended")) == 0) { *logformat = TRANSLOG_EXTENDED; } else { /* * Use transaction log basic format if * 'extended' was not specified. */ *logformat = TRANSLOG_BASIC; } } } if (strcmp(*tag, DEFAULTTAG) != 0) { /* * Use global values for fields not specified if * this tag is not the global tag. */ complete_with_global(defaultdir, bufferpath, rpclogpath, fhpath, logpath, logformat); } return (0); badtag: if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "Bad tag found in config file.")); } else { (void) fprintf(stderr, gettext( "Bad tag found in config file.\n")); } return (-1); } /* * Returns True if we have all the elements of a complete configuration * entry. A complete configuration has tag, bufferpath, fhpath and logpath * defined to non-zero strings. */ static boolean_t is_complete_config( char *tag, char *bufferpath, char *fhpath, char *logpath) { assert(tag != NULL); assert(strlen(tag) > 0); if ((bufferpath != NULL && strlen(bufferpath) > 0) && (fhpath != NULL && strlen(fhpath) > 0) && (logpath != NULL && strlen(logpath) > 0)) return (B_TRUE); return (B_FALSE); } #ifdef DEBUG /* * Prints the configuration entry to stdout. */ void nfsl_printconfig(nfsl_config_t *config) { if (config->nc_name) (void) printf("tag=%s\t", config->nc_name); if (config->nc_defaultdir) (void) printf("defaultdir=%s\t", config->nc_defaultdir); if (config->nc_logpath) (void) printf("logpath=%s\t", config->nc_logpath); if (config->nc_fhpath) (void) printf("fhpath=%s\t", config->nc_fhpath); if (config->nc_bufferpath) (void) printf("bufpath=%s\t", config->nc_bufferpath); if (config->nc_rpclogpath) (void) printf("rpclogpath=%s\t", config->nc_rpclogpath); if (config->nc_logformat == TRANSLOG_BASIC) (void) printf("logformat=basic"); else if (config->nc_logformat == TRANSLOG_EXTENDED) (void) printf("logformat=extended"); else (void) printf("config->nc_logformat=UNKNOWN"); if (config->nc_flags & NC_UPDATED) (void) printf("\tflags=NC_UPDATED"); (void) printf("\n"); } /* * Prints the configuration list to stdout. */ void nfsl_printconfig_list(nfsl_config_t *listp) { for (; listp != NULL; listp = listp->nc_next) { nfsl_printconfig(listp); (void) printf("\n"); } } #endif /* DEBUG */ /* * Returns non-zero if the given string is allowable for a tag, zero if * not. */ static int is_legal_tag(char *tag) { int i; int len; if (tag == NULL) return (0); len = strlen(tag); if (len == 0) return (0); for (i = 0; i < len; i++) { char c; c = tag[i]; if (!(isalnum((unsigned char)c) || c == '_')) return (0); } return (1); } /* * gataline attempts to get a line from the configuration file, * upto LINESZ. A line in the file is a concatenation of lines if the * continuation symbol '\' is used at the end of the line. Returns * line on success, a NULL on EOF, and an empty string on lines > linesz. */ static char * gataline(FILE *fp, char *path, char *line, int linesz) { char *p = line; int len; int excess = 0; *p = '\0'; for (;;) { if (fgets(p, linesz - (p-line), fp) == NULL) { return (*line ? line : NULL); /* EOF */ } len = strlen(line); if (len <= 0) { p = line; continue; } p = &line[len - 1]; /* * Is input line too long? */ if (*p != '\n') { excess = 1; /* * Perhaps last char read was '\'. Reinsert it * into the stream to ease the parsing when we * read the rest of the line to discard. */ (void) ungetc(*p, fp); break; } trim: /* trim trailing white space */ while (p >= line && isspace(*(uchar_t *)p)) *p-- = '\0'; if (p < line) { /* empty line */ p = line; continue; } if (*p == '\\') { /* continuation */ *p = '\0'; continue; } /* * Ignore comments. Comments start with '#' * which must be preceded by a whitespace, unless * '#' is the first character in the line. */ p = line; while ((p = strchr(p, '#')) != NULL) { if (p == line || isspace(*(p-1))) { *p-- = '\0'; goto trim; } p++; } break; } if (excess) { int c; /* * discard rest of line and return an empty string. * done to set the stream to the correct place when * we are done with this line. */ while ((c = getc(fp)) != EOF) { *p = c; if (*p == '\n') /* end of the long line */ break; else if (*p == '\\') { /* continuation */ if (getc(fp) == EOF) /* ignore next char */ break; } } if (nfsl_errs_to_syslog) { syslog(LOG_ERR, gettext( "%s: line too long - ignored (max %d chars)"), path, linesz-1); } else { (void) fprintf(stderr, gettext( "%s: line too long - ignored (max %d chars)\n"), path, linesz-1); } *line = '\0'; } return (line); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _NFS_NFSLOG_CONFIG_H #define _NFS_NFSLOG_CONFIG_H /* * Internal configuration file API for NFS logging. * * Warning: This code is likely to change drastically in future releases. */ #ifdef __cplusplus extern "C" { #endif #ifndef LINTHAPPY #define LINTHAPPY #endif #define MAX_LINESZ 4096 #define NFSL_CONFIG_FILE_PATH "/etc/nfs/nfslog.conf" #define DEFAULTTAG "global" #define DEFAULTRAWTAG "global-raw" #define DEFAULTDIR "/var/nfs" #define BUFFERPATH "nfslog_workbuffer" #define FHPATH "fhtable" #define LOGPATH "nfslog" enum translog_format { TRANSLOG_BASIC, TRANSLOG_EXTENDED }; /* * This struct is used to get or set the logging state for a filesystem. * Using a single struct like this is okay for for releases where it's * private, but it's questionable as a * public API, because of extensibility and binary compatibility issues. * * Relative paths are interpreted relative to the root of the exported * directory tree. */ typedef struct nfsl_config { uint_t nc_flags; char *nc_name; /* tag or "global" */ char *nc_defaultdir; char *nc_logpath; char *nc_fhpath; char *nc_bufferpath; char *nc_rpclogpath; enum translog_format nc_logformat; void *nc_elfcookie; /* for rpclogfile processing */ void *nc_transcookie; /* for logfile processing */ struct nfsl_config *nc_next; } nfsl_config_t; #define NC_UPDATED 0x001 /* set when an existing entry is */ /* modified after detecting changes */ /* in the configuration file. */ /* Not set on creation of entry. */ #define NC_NOTAG_PRINTED 0x002 /* 'missing tag' syslogged */ extern boolean_t nfsl_errs_to_syslog; extern int nfsl_getconfig_list(nfsl_config_t **listpp); extern int nfsl_checkconfig_list(nfsl_config_t **listpp, boolean_t *); extern void nfsl_freeconfig_list(nfsl_config_t **listpp); extern nfsl_config_t *nfsl_findconfig(nfsl_config_t *, char *, int *); #ifndef LINTHAPPY extern void nfsl_printconfig_list(nfsl_config_t *config); #endif #ifdef __cplusplus } #endif #endif /* _NFS_NFSLOG_CONFIG_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ /* * Manipulates the nfslogtab */ #ifndef _REENTRANT #define _REENTRANT #endif #include #include #include #include #include #include #include #include #include #include #include "nfslogtab.h" #ifndef LINTHAPPY #define LINTHAPPY #endif static void logtab_ent_list_free(struct logtab_ent_list *); /* * Retrieves the next entry from nfslogtab. * Assumes the file is locked. * '*lepp' points to the new entry if successful. * Returns: * > 0 valid entry * = 0 end of file * < 0 error */ int logtab_getent(FILE *fd, struct logtab_ent **lepp) { char line[MAXBUFSIZE + 1]; char *p; char *lasts, *tmp; char *w = " \t"; struct logtab_ent *lep = NULL; int error = 0; if ((lep = (struct logtab_ent *)malloc(sizeof (*lep))) == NULL) { return (-1); } (void) memset((char *)lep, 0, sizeof (*lep)); if ((p = fgets(line, MAXBUFSIZE, fd)) == NULL) { error = 0; goto errout; } line[strlen(line) - 1] = '\0'; tmp = (char *)strtok_r(p, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } if ((lep->le_buffer = strdup(tmp)) == NULL) { error = -1; goto errout; } tmp = (char *)strtok_r(NULL, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } if ((lep->le_path = strdup(tmp)) == NULL) { error = -1; goto errout; } tmp = (char *)strtok_r(NULL, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } if ((lep->le_tag = strdup(tmp)) == NULL) { error = -1; goto errout; } tmp = (char *)strtok_r(NULL, w, &lasts); if (tmp == NULL) { error = -1; goto errout; } lep->le_state = atoi(tmp); *lepp = lep; return (1); errout: logtab_ent_free(lep); return (error); } /* * Append an entry to the logtab file. */ int logtab_putent(FILE *fd, struct logtab_ent *lep) { int r; if (fseek(fd, 0L, SEEK_END) < 0) return (errno); r = fprintf(fd, "%s\t%s\t%s\t%d\n", lep->le_buffer, lep->le_path, lep->le_tag, lep->le_state); return (r); } #ifndef LINTHAPPY /* * Searches the nfslogtab file looking for the next entry which matches * the search criteria. The search is continued at the current position * in the nfslogtab file. * If 'buffer' != NULL, then buffer is matched. * If 'path' != NULL, then path is matched. * If 'tag' != NULL, then tag is matched. * If 'state' != -1, then state is matched. * 'buffer', 'path' and 'tag' can all be non-NULL, which means the entry must * satisfy all requirements. * * Returns 0 on success, ENOENT otherwise. * If found, '*lepp' points to the matching entry, otherwise '*lepp' is * undefined. */ static int logtab_findent(FILE *fd, char *buffer, char *path, char *tag, int state, struct logtab_ent **lepp) { boolean_t found = B_FALSE; while (!found && (logtab_getent(fd, lepp) > 0)) { found = B_TRUE; if (buffer != NULL) found = strcmp(buffer, (*lepp)->le_buffer) == 0; if (path != NULL) found = found && (strcmp(path, (*lepp)->le_path) == 0); if (tag != NULL) found = found && (strcmp(tag, (*lepp)->le_tag) == 0); if (state != -1) found = found && (state == (*lepp)->le_state); if (!found) logtab_ent_free(*lepp); } return (found ? 0 : ENOENT); } #endif /* * Remove all entries which match the search criteria. * If 'buffer' != NULL, then buffer is matched. * If 'path' != NULL, then path is matched. * If 'tag' != NULL, then tag is matched. * If 'state' != -1, then state is matched. * 'buffer', 'path' and 'tag' can all be non-NULL, which means the entry must * satisfy all requirements. * The file is assumed to be locked. * Read the entries into a linked list of logtab_ent structures * minus the entries to be removed, then truncate the nfslogtab * file and write it back to the file from the linked list. * * On success returns 0, -1 otherwise. * Entry not found is treated as success since it was going to be removed * anyway. */ int logtab_rement(FILE *fd, char *buffer, char *path, char *tag, int state) { struct logtab_ent_list *head = NULL, *tail = NULL, *tmpl; struct logtab_ent *lep; int remcnt = 0; /* remove count */ int error = 0; boolean_t found; rewind(fd); while ((error = logtab_getent(fd, &lep)) > 0) { found = B_TRUE; if (buffer != NULL) found = strcmp(buffer, lep->le_buffer) == 0; if (path != NULL) found = found && (strcmp(path, lep->le_path) == 0); if (tag != NULL) found = found && (strcmp(tag, lep->le_tag) == 0); if (state != -1) found = found && (state == lep->le_state); if (found) { remcnt++; logtab_ent_free(lep); } else { tmpl = (struct logtab_ent_list *) malloc(sizeof (struct logtab_ent)); if (tmpl == NULL) { error = ENOENT; break; } tmpl->lel_le = lep; tmpl->lel_next = NULL; if (head == NULL) { /* * empty list */ head = tail = tmpl; } else { /* * Add to the end of the list and remember * the new last element. */ tail->lel_next = tmpl; tail = tmpl; /* remember the last element */ } } } if (error) goto deallocate; if (remcnt == 0) { /* * Entry not found, nothing to do */ goto deallocate; } if (ftruncate(fileno(fd), 0) < 0) { error = -1; goto deallocate; } for (tmpl = head; tmpl != NULL; tmpl = tmpl->lel_next) (void) logtab_putent(fd, tmpl->lel_le); deallocate: logtab_ent_list_free(head); return (error); } /* * Deactivate all entries matching search criteria. * If 'buffer' != NULL then match buffer. * If 'path' != NULL then match path. * If 'tag' != NULL then match tag. * Note that 'buffer', 'path' and 'tag' can al be non-null at the same time. * * Rewrites the nfslogtab file with the updated state for each entry. * Assumes the nfslogtab file has been locked for writing. * Returns 0 on success, -1 on failure. */ int logtab_deactivate(FILE *fd, char *buffer, char *path, char *tag) { struct logtab_ent_list *lelp, *head = NULL, *tail = NULL; struct logtab_ent *lep; boolean_t found; int error = 0; int count = 0; rewind(fd); while ((error = logtab_getent(fd, &lep)) > 0) { found = B_TRUE; if (buffer != NULL) found = strcmp(buffer, lep->le_buffer) == 0; if (path != NULL) found = found && (strcmp(path, lep->le_path) == 0); if (tag != NULL) found = found && (strcmp(tag, lep->le_tag) == 0); if (found && (lep->le_state == LES_ACTIVE)) { count++; lep->le_state = LES_INACTIVE; } lelp = (struct logtab_ent_list *) malloc(sizeof (struct logtab_ent)); if (lelp == NULL) { error = ENOENT; break; } lelp->lel_le = lep; lelp->lel_next = NULL; if (head == NULL) { /* * empty list */ head = tail = lelp; } else { /* * Add to the end of the list and remember * the new last element. */ tail->lel_next = lelp; tail = lelp; /* remember the last element */ } } if (error) goto deallocate; if (count == 0) { /* * done */ error = 0; goto deallocate; } if (ftruncate(fileno(fd), 0) < 0) { error = -1; goto deallocate; } for (lelp = head; lelp != NULL; lelp = lelp->lel_next) (void) logtab_putent(fd, lelp->lel_le); deallocate: logtab_ent_list_free(head); return (error); } /* * Deactivates all entries if nfslogtab exists and is older than boot time * This will only happen the first time it is called. * Assumes 'fd' has been locked by the caller. * Returns 0 on success, otherwise -1. */ int logtab_deactivate_after_boot(FILE *fd) { struct stat st; struct utmpx *utmpxp; int error = 0; if ((fstat(fileno(fd), &st) == 0) && ((utmpxp = getutxent()) != NULL) && (utmpxp->ut_xtime > st.st_mtime)) { if (logtab_deactivate(fd, NULL, NULL, NULL)) error = -1; } return (error); } void logtab_ent_free(struct logtab_ent *lep) { if (lep->le_buffer) free(lep->le_buffer); if (lep->le_path) free(lep->le_path); if (lep->le_tag) free(lep->le_tag); free(lep); } static void logtab_ent_list_free(struct logtab_ent_list *head) { struct logtab_ent_list *lelp, *next; if (head == NULL) return; for (lelp = head; lelp != NULL; lelp = next) { if (lelp->lel_le != NULL) logtab_ent_free(lelp->lel_le); next = lelp->lel_next; free(lelp); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999, 2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _NFSLOGTAB_H #define _NFSLOGTAB_H /* * Defines logtab */ #ifdef __cplusplus extern "C" { #endif #include #define NFSLOGTAB "/etc/nfs/nfslogtab" #define MAXBUFSIZE 65536 #define LES_INACTIVE 0 /* entry is inactive */ #define LES_ACTIVE 1 /* entry is active */ struct logtab_ent { char *le_buffer; char *le_path; char *le_tag; int le_state; }; struct logtab_ent_list { struct logtab_ent *lel_le; struct logtab_ent_list *lel_next; }; int logtab_getent(FILE *, struct logtab_ent **); int logtab_putent(FILE *, struct logtab_ent *); int logtab_rement(FILE *, char *, char *, char *, int); void logtab_ent_free(struct logtab_ent *lep); int logtab_deactivate(FILE *, char *, char *, char *); int logtab_deactivate_after_boot(FILE *); #ifdef __cplusplus } #endif #endif /* _NFSLOGTAB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * str_to_utf8 - converts a null-terminated C string to a utf8 string */ utf8string * str_to_utf8(char *nm, utf8string *str) { int len; if (str == NULL) return (NULL); if (nm == NULL || *nm == '\0') { str->utf8string_len = 0; str->utf8string_val = NULL; return (NULL); } len = strlen(nm); str->utf8string_val = malloc(len); if (str->utf8string_val == NULL) { str->utf8string_len = 0; return (NULL); } str->utf8string_len = len; bcopy(nm, str->utf8string_val, len); return (str); } /* * Converts a utf8 string to a C string. * kmem_allocs a new string if not supplied */ char * utf8_to_str(utf8string *str, uint_t *lenp, char *s) { char *sp; char *u8p; int len; int i; if (str == NULL) return (NULL); u8p = str->utf8string_val; len = str->utf8string_len; if (len <= 0 || u8p == NULL) { if (s) *s = '\0'; return (NULL); } sp = s; if (sp == NULL) sp = malloc(len + 1); if (sp == NULL) return (NULL); /* * At least check for embedded nulls */ for (i = 0; i < len; i++) { sp[i] = u8p[i]; if (u8p[i] == '\0') { if (s == NULL) free(sp); return (NULL); } } sp[len] = '\0'; *lenp = len + 1; return (sp); } void print_referral_summary(fs_locations4 *fsl) { int i, j; uint_t l; char *s; fs_location4 *fs; if (fsl == NULL) { printf("NULL\n"); return; } for (i = 0; i < fsl->locations.locations_len; i++) { if (i > 0) printf("\n"); fs = &fsl->locations.locations_val[i]; for (j = 0; j < fs->server.server_len; j++) { s = utf8_to_str(&fs->server.server_val[j], &l, NULL); if (j > 0) printf(","); printf("%s", s ? s : ""); if (s) free(s); } printf(":"); for (j = 0; j < fs->rootpath.pathname4_len; j++) { s = utf8_to_str(&fs->rootpath.pathname4_val[j], &l, NULL); printf("/%s", s ? s : ""); if (s) free(s); } if (fs->rootpath.pathname4_len == 0) printf("/"); } printf("\n"); } /* * There is a kernel copy of this routine in nfs4_srv.c. * Changes should be kept in sync. */ static int nfs4_create_components(char *path, component4 *comp4) { int slen, plen, ncomp; char *ori_path, *nxtc, buf[MAXNAMELEN]; if (path == NULL) return (0); plen = strlen(path) + 1; /* include the terminator */ ori_path = path; ncomp = 0; /* count number of components in the path */ for (nxtc = path; nxtc < ori_path + plen; nxtc++) { if (*nxtc == '/' || *nxtc == '\0' || *nxtc == '\n') { if ((slen = nxtc - path) == 0) { path = nxtc + 1; continue; } if (comp4 != NULL) { bcopy(path, buf, slen); buf[slen] = '\0'; if (str_to_utf8(buf, &comp4[ncomp]) == NULL) return (0); } ncomp++; /* 1 valid component */ path = nxtc + 1; } if (*nxtc == '\0' || *nxtc == '\n') break; } return (ncomp); } /* * There is a kernel copy of this routine in nfs4_srv.c. * Changes should be kept in sync. */ int make_pathname4(char *path, pathname4 *pathname) { int ncomp; component4 *comp4; if (pathname == NULL) return (0); if (path == NULL) { pathname->pathname4_val = NULL; pathname->pathname4_len = 0; return (0); } /* count number of components to alloc buffer */ if ((ncomp = nfs4_create_components(path, NULL)) == 0) { pathname->pathname4_val = NULL; pathname->pathname4_len = 0; return (0); } comp4 = calloc(ncomp * sizeof (component4), 1); if (comp4 == NULL) { pathname->pathname4_val = NULL; pathname->pathname4_len = 0; return (0); } /* copy components into allocated buffer */ ncomp = nfs4_create_components(path, comp4); pathname->pathname4_val = comp4; pathname->pathname4_len = ncomp; return (ncomp); } bool_t xdr_component4(register XDR *xdrs, component4 *objp) { if (!xdr_utf8string(xdrs, objp)) return (FALSE); return (TRUE); } bool_t xdr_utf8string(register XDR *xdrs, utf8string *objp) { if (xdrs->x_op != XDR_FREE) return (xdr_bytes(xdrs, (char **)&objp->utf8string_val, (uint_t *)&objp->utf8string_len, NFS4_MAX_UTF8STRING)); return (TRUE); } bool_t xdr_pathname4(register XDR *xdrs, pathname4 *objp) { if (!xdr_array(xdrs, (char **)&objp->pathname4_val, (uint_t *)&objp->pathname4_len, NFS4_MAX_PATHNAME4, sizeof (component4), (xdrproc_t)xdr_component4)) return (FALSE); return (TRUE); } bool_t xdr_fs_location4(register XDR *xdrs, fs_location4 *objp) { if (xdrs->x_op == XDR_DECODE) { objp->server.server_val = NULL; objp->rootpath.pathname4_val = NULL; } if (!xdr_array(xdrs, (char **)&objp->server.server_val, (uint_t *)&objp->server.server_len, ~0, sizeof (utf8string), (xdrproc_t)xdr_utf8string)) return (FALSE); if (!xdr_pathname4(xdrs, &objp->rootpath)) return (FALSE); return (TRUE); } bool_t xdr_fs_locations4(register XDR *xdrs, fs_locations4 *objp) { if (xdrs->x_op == XDR_DECODE) { objp->fs_root.pathname4_len = 0; objp->fs_root.pathname4_val = NULL; objp->locations.locations_val = NULL; } if (!xdr_pathname4(xdrs, &objp->fs_root)) return (FALSE); if (!xdr_array(xdrs, (char **)&objp->locations.locations_val, (uint_t *)&objp->locations.locations_len, ~0, sizeof (fs_location4), (xdrproc_t)xdr_fs_location4)) return (FALSE); return (TRUE); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _REF_SUBR_H #define _REF_SUBR_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include extern utf8string *str_to_utf8(char *, utf8string *); extern char *utf8_to_str(utf8string *, uint_t *, char *); extern void print_referral_summary(fs_locations4 *); extern int make_pathname4(char *, pathname4 *); extern bool_t xdr_component4(register XDR *, component4 *); extern bool_t xdr_utf8string(register XDR *, utf8string *); extern bool_t xdr_pathname4(register XDR *, pathname4 *); extern bool_t xdr_fs_location4(register XDR *, fs_location4 *); extern bool_t xdr_fs_locations4(register XDR *, fs_locations4 *); #ifdef __cplusplus } #endif #endif /* _REF_SUBR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * replica.c * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Parse replicated server lists of the form: * * host1:/path1,host2,host3,host4:/path2,host5:/path3 * * into an array containing its constituent parts: * * host1 /path1 * host2 /path2 * host3 /path2 * host4 /path2 * host5 /path3 * where a server could also be represented in form of literal address * and in case it is an IPv6 literal address it will be enclosed in * square brackets [IPv6 Literal address] * Problems indicated by null return; they will be memory allocation * errors worthy of an error message unless count == -1, which means * a parse error. */ #include #include #include #include #include #include #include "replica.h" void free_replica(struct replica *list, int count) { int i; for (i = 0; i < count; i++) { if (list[i].host) free(list[i].host); if (list[i].path) free(list[i].path); } free(list); } struct replica * parse_replica(char *special, int *count) { struct replica *list = NULL; char *root, *special2; char *proot, *x, *y; int scount, v6addr, i; int found_colon = 0; *count = 0; scount = 0; v6addr = 0; root = special2 = strdup(special); proot = root; while (root) { switch (*root) { case '[': if ((root != special2) && (*(root -1) != ',')) { root++; break; } y = strchr(root, ']'); if (!y) { root++; break; } if ((*(y + 1) != ',') && (*(y + 1) != ':')) { root = y + 1; break; } /* * Found a v6 Literal Address, so set "v6addr" * and grab the address and store it in the list * under "host part". */ proot = root + 1; root = y + 1; v6addr = 1; if ((list = realloc(list, (*count + 1) * sizeof (struct replica))) == NULL) goto bad; bzero(&list[(*count)++], sizeof (struct replica)); *y = '\0'; list[*count-1].host = strdup(proot); if (!list[*count-1].host) goto bad; break; case ':': *root = '\0'; x = root + 1; /* * Find comma (if present), which bounds the path. * The comma implies that the user is trying to * specify failover syntax if another colon follows. */ if (((y = strchr(x, ',')) != NULL) && (strchr((y + 1), ':'))) { root = y + 1; *y = '\0'; } else { found_colon = 1; root = NULL; } /* * If "v6addr" is set, unset it, and since the "host * part" is already taken care of, skip to the "path * path" part. */ if (v6addr == 1) v6addr = 0; else { if ((list = realloc(list, (*count + 1) * sizeof (struct replica))) == NULL) goto bad; bzero(&list[(*count)++], sizeof (struct replica)); list[*count-1].host = strdup(proot); if (!list[*count-1].host) goto bad; proot = root; } for (i = scount; i < *count; i++) { list[i].path = strdup(x); if (!list[i].path) goto bad; } scount = i; proot = root; if (y) *y = ','; break; case ',': /* * If "v6addr" is set, unset it and continue * else grab the address and store it in the list * under "host part". */ if (v6addr == 1) { v6addr = 0; proot = ++root; } else { *root = '\0'; root++; if ((list = realloc(list, (*count + 1) * sizeof (struct replica))) == NULL) goto bad; bzero(&list[(*count)++], sizeof (struct replica)); list[*count-1].host = strdup(proot); if (!list[*count-1].host) goto bad; proot = root; *(root - 1) = ','; } break; default: if (*root == '\0') root = NULL; else root++; } } if (found_colon) { free(special2); return (list); } bad: if (list) free_replica(list, *count); if (!found_colon) *count = -1; free(special2); return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * replica.h * * Copyright (c) 1996 Sun Microsystems Inc * All Rights Reserved. */ #ifndef _REPLICA_H #define _REPLICA_H #ifdef __cplusplus extern "C" { #endif /* * Used to hold results of parsing replica lists for failover */ struct replica { char *host; char *path; }; struct replica *parse_replica(char *, int *); void free_replica(struct replica *, int); #ifdef __cplusplus } #endif #endif /* _REPLICA_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * selfcheck.c * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include int self_check(char *hostname) { int s, res = 0; struct sioc_addrreq areq; struct hostent *hostinfo; int family; int flags; int error_num; char **hostptr; struct sockaddr_in6 ipv6addr; family = AF_INET6; /* * We cannot specify AI_DEFAULT since it includes AI_ADDRCONFIG. * Localhost name resolution will fail if no IP interfaces other than * loopback are plumbed and AI_ADDRCONFIG is specified, and this * causes localhost mounts to fail. */ flags = AI_V4MAPPED; if ((s = socket(family, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "self_check: socket: %m"); return (0); } if ((hostinfo = getipnodebyname(hostname, family, flags, &error_num)) == NULL) { if (error_num == TRY_AGAIN) syslog(LOG_DEBUG, "self_check: unknown host: %s (try again later)\n", hostname); else syslog(LOG_DEBUG, "self_check: unknown host: %s\n", hostname); (void) close(s); return (0); } for (hostptr = hostinfo->h_addr_list; *hostptr; hostptr++) { bzero(&ipv6addr, sizeof (ipv6addr)); ipv6addr.sin6_family = AF_INET6; ipv6addr.sin6_addr = *((struct in6_addr *)(*hostptr)); memcpy(&areq.sa_addr, (void *)&ipv6addr, sizeof (ipv6addr)); areq.sa_res = -1; (void) ioctl(s, SIOCTMYADDR, (caddr_t)&areq); if (areq.sa_res == 1) { res = 1; break; } } freehostent(hostinfo); (void) close(s); return (res); } #define MAXIFS 32 /* * create an ifconf structure that represents all the interfaces * configured for this host. Two buffers are allcated here: * lifc - the ifconf structure returned * lifc->lifc_buf - the list of ifreq structures * Both of the buffers must be freed by the calling routine. * A NULL pointer is returned upon failure. In this case any * data that was allocated before the failure has already been * freed. */ struct lifconf * getmyaddrs(void) { int sock; struct lifnum lifn; int numifs; char *buf; struct lifconf *lifc; if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "statd:getmyaddrs socket: %m"); return ((struct lifconf *)NULL); } lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { syslog(LOG_ERR, "statd:getmyaddrs, get number of interfaces, error: %m"); numifs = MAXIFS; } numifs = lifn.lifn_count; lifc = (struct lifconf *)malloc(sizeof (struct lifconf)); if (lifc == NULL) { syslog(LOG_ERR, "statd:getmyaddrs, malloc for lifconf failed: %m"); (void) close(sock); return ((struct lifconf *)NULL); } buf = (char *)malloc(numifs * sizeof (struct lifreq)); if (buf == NULL) { syslog(LOG_ERR, "statd:getmyaddrs, malloc for lifreq failed: %m"); (void) close(sock); free(lifc); return ((struct lifconf *)NULL); } lifc->lifc_family = AF_UNSPEC; lifc->lifc_flags = 0; lifc->lifc_buf = buf; lifc->lifc_len = numifs * sizeof (struct lifreq); if (ioctl(sock, SIOCGLIFCONF, (char *)lifc) < 0) { syslog(LOG_ERR, "statd:getmyaddrs, SIOCGLIFCONF, error: %m"); (void) close(sock); free(buf); free(lifc); return ((struct lifconf *)NULL); } (void) close(sock); return (lifc); } int Is_ipv6present(void) { int sock; struct lifnum lifn; sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock < 0) return (0); lifn.lifn_family = AF_INET6; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { close(sock); return (0); } close(sock); if (lifn.lifn_count == 0) return (0); return (1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include #include #include #include #include #include "sharetab.h" /* * Get an entry from the share table. * There should be at least 4 fields: * * pathname resource fstype options [ description ] * * A fifth field (description) is optional. * * Returns: * > 1 valid entry * = 0 end of file * < 0 error */ int getshare(FILE *fd, share_t **shp) { static char *line = NULL; static share_t *sh = NULL; char *p; char *lasts; char *w = " \t"; if (line == NULL) { line = (char *)malloc(MAXBUFSIZE+1); if (line == NULL) return (-1); } if (sh == NULL) { sh = (share_t *)malloc(sizeof (*sh)); if (sh == NULL) return (-1); } p = fgets(line, MAXBUFSIZE, fd); if (p == NULL) return (0); line[strlen(line) - 1] = '\0'; sh->sh_path = (char *)strtok_r(p, w, &lasts); if (sh->sh_path == NULL) return (-3); sh->sh_res = (char *)strtok_r(NULL, w, &lasts); if (sh->sh_res == NULL) return (-3); sh->sh_fstype = (char *)strtok_r(NULL, w, &lasts); if (sh->sh_fstype == NULL) return (-3); sh->sh_opts = (char *)strtok_r(NULL, w, &lasts); if (sh->sh_opts == NULL) return (-3); sh->sh_descr = (char *)strtok_r(NULL, "", &lasts); if (sh->sh_descr == NULL) sh->sh_descr = ""; *shp = sh; return (1); } share_t * sharedup(share_t *sh) { share_t *nsh; nsh = (share_t *)calloc(1, sizeof (*nsh)); if (nsh == NULL) return (NULL); if (sh->sh_path) { nsh->sh_path = strdup(sh->sh_path); if (nsh->sh_path == NULL) goto alloc_failed; } if (sh->sh_res) { nsh->sh_res = strdup(sh->sh_res); if (nsh->sh_res == NULL) goto alloc_failed; } if (sh->sh_fstype) { nsh->sh_fstype = strdup(sh->sh_fstype); if (nsh->sh_fstype == NULL) goto alloc_failed; } if (sh->sh_opts) { nsh->sh_opts = strdup(sh->sh_opts); if (nsh->sh_opts == NULL) goto alloc_failed; } if (sh->sh_descr) { nsh->sh_descr = strdup(sh->sh_descr); if (nsh->sh_descr == NULL) goto alloc_failed; } return (nsh); alloc_failed: sharefree(nsh); return (NULL); } void sharefree(share_t *sh) { if (sh->sh_path != NULL) free(sh->sh_path); if (sh->sh_res != NULL) free(sh->sh_res); if (sh->sh_fstype != NULL) free(sh->sh_fstype); if (sh->sh_opts != NULL) free(sh->sh_opts); if (sh->sh_descr != NULL) free(sh->sh_descr); free(sh); } /* * Return the value after "=" for option "opt" * in option string "optlist". Caller must * free returned value. */ char * getshareopt(char *optlist, char *opt) { char *p, *pe; char *b; char *bb; char *lasts; char *val = NULL; b = bb = strdup(optlist); if (b == NULL) return (NULL); while ((p = strtok_r(b, ",", &lasts)) != NULL) { b = NULL; if ((pe = strchr(p, '=')) != NULL) { *pe = '\0'; if (strcmp(opt, p) == 0) { val = strdup(pe + 1); goto done; } } if (strcmp(opt, p) == 0) { val = strdup(""); goto done; } } done: free(bb); return (val); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Note: must be included before this file. */ #ifndef _SHARETAB_H #define _SHARETAB_H #ifdef __cplusplus extern "C" { #endif #define SHOPT_RO "ro" #define SHOPT_RW "rw" #define SHOPT_NONE "none" #define SHOPT_ROOT_MAPPING "root_mapping" #define SHOPT_SEC "sec" #define SHOPT_SECURE "secure" #define SHOPT_ROOT "root" #define SHOPT_ANON "anon" #define SHOPT_WINDOW "window" #define SHOPT_NOSUB "nosub" #define SHOPT_NOSUID "nosuid" #define SHOPT_ACLOK "aclok" #define SHOPT_PUBLIC "public" #define SHOPT_INDEX "index" #define SHOPT_LOG "log" #define SHOPT_NOACLFAB "noaclfab" #define SHOPT_UIDMAP "uidmap" #define SHOPT_GIDMAP "gidmap" #define SHOPT_NOHIDE "nohide" /* XXX The following are added for testing volatile fh's purposes only */ #ifdef VOLATILE_FH_TEST #define SHOPT_VOLFH "volfh" #endif /* VOLATILE_FH_TEST */ int getshare(FILE *, share_t **); char *getshareopt(char *, char *); share_t *sharedup(share_t *); void sharefree(share_t *); #ifdef __cplusplus } #endif #endif /* !_SHARETAB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include "smfcfg.h" /* * NFS version strings translation table to numeric form. */ static struct str_val { const char *str; uint32_t val; } nfs_versions[] = { { "2", NFS_VERS_2 }, { "3", NFS_VERS_3 }, { "4", NFS_VERS_4 }, { "4.0", NFS_VERS_4 }, { "4.1", NFS_VERS_4_1 }, { "4.2", NFS_VERS_4_2 } }; /* * Translate NFS version string to numeric form. * Returns NFS_VERS_... value or zero for invalid version string. */ uint32_t nfs_convert_version_str(const char *version) { uint32_t v = 0; for (size_t i = 0; i < ARRAY_SIZE(nfs_versions); i++) { if (strcmp(version, nfs_versions[i].str) == 0) { v = nfs_versions[i].val; break; } } return (v); } fs_smfhandle_t * fs_smf_init(const char *fmri, const char *instance) { fs_smfhandle_t *handle = NULL; char *svcname, srv[MAXPATHLEN]; /* * svc name is of the form svc://network/fs/server:instance1 * FMRI portion is /network/fs/server */ (void) snprintf(srv, MAXPATHLEN, "%s", fmri + strlen("svc:/")); svcname = strrchr(srv, ':'); if (svcname != NULL) *svcname = '\0'; svcname = srv; handle = calloc(1, sizeof (fs_smfhandle_t)); if (handle != NULL) { handle->fs_handle = scf_handle_create(SCF_VERSION); if (handle->fs_handle == NULL) goto out; if (scf_handle_bind(handle->fs_handle) != 0) goto out; handle->fs_service = scf_service_create(handle->fs_handle); handle->fs_scope = scf_scope_create(handle->fs_handle); if (scf_handle_get_local_scope(handle->fs_handle, handle->fs_scope) != 0) goto out; if (scf_scope_get_service(handle->fs_scope, svcname, handle->fs_service) != SCF_SUCCESS) { goto out; } handle->fs_pg = scf_pg_create(handle->fs_handle); handle->fs_instance = scf_instance_create(handle->fs_handle); handle->fs_property = scf_property_create(handle->fs_handle); handle->fs_value = scf_value_create(handle->fs_handle); } else { fprintf(stderr, gettext("Cannot access SMF repository: %s\n"), fmri); } return (handle); out: fs_smf_fini(handle); if (scf_error() != SCF_ERROR_NOT_FOUND) { fprintf(stderr, gettext("SMF Initialization problem(%s): %s\n"), fmri, scf_strerror(scf_error())); } return (NULL); } void fs_smf_fini(fs_smfhandle_t *handle) { if (handle != NULL) { scf_scope_destroy(handle->fs_scope); scf_instance_destroy(handle->fs_instance); scf_service_destroy(handle->fs_service); scf_pg_destroy(handle->fs_pg); scf_property_destroy(handle->fs_property); scf_value_destroy(handle->fs_value); if (handle->fs_handle != NULL) { (void) scf_handle_unbind(handle->fs_handle); scf_handle_destroy(handle->fs_handle); } free(handle); } } int fs_smf_set_prop(smf_fstype_t fstype, char *prop_name, char *valbuf, char *instance, scf_type_t sctype, char *fmri) { fs_smfhandle_t *phandle = NULL; scf_handle_t *handle; scf_propertygroup_t *pg; scf_property_t *prop; scf_transaction_t *tran = NULL; scf_transaction_entry_t *entry = NULL; scf_instance_t *inst; scf_value_t *val; int valint; int ret = 0; char *p = NULL; char *svcname, srv[MAXPATHLEN]; const char *pgname; /* * The SVC names we are using currently are already * appended by default. Fix this for instances project. */ (void) snprintf(srv, MAXPATHLEN, "%s", fmri); p = strstr(fmri, ":default"); if (p == NULL) { (void) strcat(srv, ":"); if (instance == NULL) instance = "default"; if (strlen(srv) + strlen(instance) > MAXPATHLEN) goto out; (void) strncat(srv, instance, strlen(instance)); } svcname = srv; phandle = fs_smf_init(fmri, instance); if (phandle == NULL) { return (SMF_SYSTEM_ERR); } handle = phandle->fs_handle; pg = phandle->fs_pg; prop = phandle->fs_property; inst = phandle->fs_instance; val = phandle->fs_value; tran = scf_transaction_create(handle); entry = scf_entry_create(handle); if (handle == NULL || pg == NULL || prop == NULL || val == NULL|| tran == NULL || entry == NULL || inst == NULL) { ret = SMF_SYSTEM_ERR; goto out; } if (scf_handle_decode_fmri(handle, svcname, phandle->fs_scope, phandle->fs_service, inst, NULL, NULL, 0) != 0) { ret = scf_error(); goto out; } if (fstype == AUTOFS_SMF) pgname = AUTOFS_PROPS_PGNAME; else pgname = NFS_PROPS_PGNAME; if (scf_instance_get_pg(inst, pgname, pg) != -1) { uint8_t vint; if (scf_transaction_start(tran, pg) == -1) { ret = scf_error(); goto out; } switch (sctype) { case SCF_TYPE_INTEGER: errno = 0; valint = strtoul(valbuf, NULL, 0); if (errno != 0) { ret = SMF_SYSTEM_ERR; goto out; } if (scf_transaction_property_change(tran, entry, prop_name, SCF_TYPE_INTEGER) == 0) { scf_value_set_integer(val, valint); if (scf_entry_add_value(entry, val) < 0) { ret = scf_error(); goto out; } } break; case SCF_TYPE_ASTRING: if (scf_transaction_property_change(tran, entry, prop_name, SCF_TYPE_ASTRING) == 0) { if (scf_value_set_astring(val, valbuf) == 0) { if (scf_entry_add_value(entry, val) != 0) { ret = scf_error(); goto out; } } else ret = SMF_SYSTEM_ERR; } else ret = SMF_SYSTEM_ERR; break; case SCF_TYPE_BOOLEAN: if (strcmp(valbuf, "1") == 0) { vint = 1; } else if (strcmp(valbuf, "0") == 0) { vint = 0; } else { ret = SMF_SYSTEM_ERR; break; } if (scf_transaction_property_change(tran, entry, prop_name, SCF_TYPE_BOOLEAN) == 0) { scf_value_set_boolean(val, (uint8_t)vint); if (scf_entry_add_value(entry, val) != 0) { ret = scf_error(); goto out; } } else { ret = SMF_SYSTEM_ERR; } break; default: break; } if (ret != SMF_SYSTEM_ERR) (void) scf_transaction_commit(tran); } out: if (tran != NULL) scf_transaction_destroy(tran); if (entry != NULL) scf_entry_destroy(entry); fs_smf_fini(phandle); return (ret); } int fs_smf_get_prop(smf_fstype_t fstype, char *prop_name, char *cbuf, char *instance, scf_type_t sctype, char *fmri, int *bufsz) { fs_smfhandle_t *phandle = NULL; scf_handle_t *handle; scf_propertygroup_t *pg; scf_property_t *prop; scf_value_t *val; scf_instance_t *inst; int ret = 0, len = 0, length; int64_t valint = 0; char srv[MAXPATHLEN], *p, *svcname; const char *pgname; uint8_t bval; /* * The SVC names we are using currently are already * appended by default. Fix this for instances project. */ (void) snprintf(srv, MAXPATHLEN, "%s", fmri); p = strstr(fmri, ":default"); if (p == NULL) { (void) strcat(srv, ":"); if (instance == NULL) instance = "default"; if (strlen(srv) + strlen(instance) > MAXPATHLEN) goto out; (void) strncat(srv, instance, strlen(instance)); } svcname = srv; phandle = fs_smf_init(fmri, instance); if (phandle == NULL) return (SMF_SYSTEM_ERR); handle = phandle->fs_handle; pg = phandle->fs_pg; inst = phandle->fs_instance; prop = phandle->fs_property; val = phandle->fs_value; if (handle == NULL || pg == NULL || prop == NULL || val == NULL || inst == NULL) { return (SMF_SYSTEM_ERR); } if (scf_handle_decode_fmri(handle, svcname, phandle->fs_scope, phandle->fs_service, inst, NULL, NULL, 0) != 0) { ret = scf_error(); goto out; } if (fstype == AUTOFS_SMF) pgname = AUTOFS_PROPS_PGNAME; else pgname = NFS_PROPS_PGNAME; if (scf_instance_get_pg(inst, pgname, pg) != -1) { if (scf_pg_get_property(pg, prop_name, prop) != SCF_SUCCESS) { ret = scf_error(); goto out; } if (scf_property_get_value(prop, val) != SCF_SUCCESS) { ret = scf_error(); goto out; } switch (sctype) { case SCF_TYPE_ASTRING: len = scf_value_get_astring(val, cbuf, *bufsz); if (len < 0 || len > *bufsz) { ret = scf_error(); goto out; } ret = 0; *bufsz = len; break; case SCF_TYPE_INTEGER: if (scf_value_get_integer(val, &valint) != 0) { ret = scf_error(); goto out; } length = snprintf(cbuf, *bufsz, "%lld", valint); if (length < 0 || length > *bufsz) { ret = SA_BAD_VALUE; goto out; } ret = 0; break; case SCF_TYPE_BOOLEAN: if (scf_value_get_boolean(val, &bval) != 0) { ret = scf_error(); goto out; } if (bval == 1) { length = snprintf(cbuf, *bufsz, "%s", "true"); } else { length = snprintf(cbuf, *bufsz, "%s", "false"); } if (length < 0 || length > *bufsz) { ret = SA_BAD_VALUE; goto out; } break; default: break; } } else { ret = scf_error(); } if ((ret != 0) && scf_error() != SCF_ERROR_NONE) fprintf(stdout, gettext("%s\n"), scf_strerror(ret)); out: fs_smf_fini(phandle); return (ret); } int nfs_smf_get_prop(char *prop_name, char *propbuf, char *instance, scf_type_t sctype, char *svc_name, int *bufsz) { return (fs_smf_get_prop(NFS_SMF, prop_name, propbuf, instance, sctype, svc_name, bufsz)); } /* Get an integer (base 10) property */ int nfs_smf_get_iprop(char *prop_name, int *rvp, char *instance, scf_type_t sctype, char *svc_name) { char propbuf[32]; int bufsz, rc, val; bufsz = sizeof (propbuf); rc = fs_smf_get_prop(NFS_SMF, prop_name, propbuf, instance, sctype, svc_name, &bufsz); if (rc != SA_OK) return (rc); errno = 0; val = strtol(propbuf, NULL, 10); if (errno != 0) return (SA_BAD_VALUE); *rvp = val; return (SA_OK); } int nfs_smf_set_prop(char *prop_name, char *value, char *instance, scf_type_t type, char *svc_name) { return (fs_smf_set_prop(NFS_SMF, prop_name, value, instance, type, svc_name)); } int autofs_smf_set_prop(char *prop_name, char *value, char *instance, scf_type_t type, char *svc_name) { return (fs_smf_set_prop(AUTOFS_SMF, prop_name, value, instance, type, svc_name)); } int autofs_smf_get_prop(char *prop_name, char *propbuf, char *instance, scf_type_t sctype, char *svc_name, int *bufsz) { return (fs_smf_get_prop(AUTOFS_SMF, prop_name, propbuf, instance, sctype, svc_name, bufsz)); } boolean_t string_to_boolean(const char *str) { if (strcasecmp(str, "true") == 0 || atoi(str) == 1 || strcasecmp(str, "on") == 0 || strcasecmp(str, "yes") == 0) { return (B_TRUE); } else return (B_FALSE); } /* * upgrade server_versmin and server_versmax from int to string. * This is needed to allow to specify version as major.minor. */ static void nfs_upgrade_server_vers(const char *fmri) { fs_smfhandle_t *phandle; scf_handle_t *handle; scf_propertygroup_t *pg; scf_instance_t *inst; scf_value_t *vmin = NULL, *vmax = NULL; scf_transaction_t *tran = NULL; scf_transaction_entry_t *emin = NULL, *emax = NULL; char versmax[32]; char versmin[32]; int bufsz; /* * Read old integer values, stop in case of error - apparently * the upgrade is already done. */ bufsz = sizeof (versmax); if (nfs_smf_get_prop("server_versmax", versmax, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, (char *)fmri, &bufsz) != SA_OK) { return; } bufsz = sizeof (versmin); if (nfs_smf_get_prop("server_versmin", versmin, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, (char *)fmri, &bufsz) != SA_OK) { return; } /* Write back as SCF_TYPE_ASTRING */ phandle = fs_smf_init(fmri, NULL); if (phandle == NULL) return; handle = phandle->fs_handle; if (handle == NULL) goto done; pg = phandle->fs_pg; inst = phandle->fs_instance; tran = scf_transaction_create(handle); vmin = scf_value_create(handle); vmax = scf_value_create(handle); emin = scf_entry_create(handle); emax = scf_entry_create(handle); if (pg == NULL || inst == NULL || tran == NULL || emin == NULL || emax == NULL || vmin == NULL || vmax == NULL) { goto done; } if (scf_handle_decode_fmri(handle, (char *)fmri, phandle->fs_scope, phandle->fs_service, inst, NULL, NULL, 0) != 0) { goto done; } if (scf_instance_get_pg(inst, NFS_PROPS_PGNAME, pg) == -1) goto done; if (scf_pg_update(pg) == -1) goto done; if (scf_transaction_start(tran, pg) == -1) goto done; if (scf_transaction_property_change_type(tran, emax, "server_versmax", SCF_TYPE_ASTRING) != 0) { goto done; } if (scf_value_set_astring(vmax, versmax) == 0) { if (scf_entry_add_value(emax, vmax) != 0) goto done; } else { goto done; } if (scf_transaction_property_change_type(tran, emin, "server_versmin", SCF_TYPE_ASTRING) != 0) { goto done; } if (scf_value_set_astring(vmin, versmin) == 0) { if (scf_entry_add_value(emin, vmin) != 0) goto done; } else { goto done; } (void) scf_transaction_commit(tran); done: if (tran != NULL) scf_transaction_destroy(tran); if (emin != NULL) scf_entry_destroy(emin); if (emax != NULL) scf_entry_destroy(emax); if (vmin != NULL) scf_value_destroy(vmin); if (vmax != NULL) scf_value_destroy(vmax); fs_smf_fini(phandle); } void nfs_config_upgrade(const char *svc_name) { if (strcmp(svc_name, NFSD) == 0) { nfs_upgrade_server_vers(svc_name); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _SMFCFG_H #define _SMFCFG_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef enum { NFS_SMF = 1, AUTOFS_SMF } smf_fstype_t; typedef struct fs_smfhandle { scf_handle_t *fs_handle; scf_service_t *fs_service; scf_scope_t *fs_scope; scf_instance_t *fs_instance; scf_propertygroup_t *fs_pg; scf_property_t *fs_property; scf_value_t *fs_value; } fs_smfhandle_t; #define DEFAULT_INSTANCE "default" /* * NFS Property Group names. */ #define SMF_PG_NFSPROPS ((const char *)"com.oracle.nfs,props") #define NFS_PROPS_PGNAME ((const char *)"nfs-props") #define SVC_NFS_CLIENT "svc:/network/nfs/client" /* * AUTOFS Property Group Names. */ #define SMF_PG_AUTOFS ((const char *)"com.oracle.autofs,props") #define AUTOFS_PROPS_PGNAME ((const char *)"autofs-props") #define AUTOFS_FMRI "svc:/system/filesystem/autofs" #define AUTOFS_DEFAULT_FMRI "svc:/system/filesystem/autofs:default" #define MAXDIGITS 32 /* * ERRORS */ #define SMF_OK 0 #define SMF_SYSTEM_ERR -1 #define STATE_INITIALIZING 1 #define SMF_NO_PERMISSION 2 #define SMF_NO_PGTYPE 3 extern uint32_t nfs_convert_version_str(const char *); extern void nfs_config_upgrade(const char *); extern int nfs_smf_get_iprop(char *, int *, char *, scf_type_t, char *); extern int nfs_smf_get_prop(char *, char *, char *, scf_type_t, char *, int *); extern int fs_smf_get_prop(smf_fstype_t, char *, char *, char *, scf_type_t, char *, int *); extern int nfs_smf_set_prop(char *, char *, char *, scf_type_t, char *); extern int fs_smf_set_prop(smf_fstype_t, char *, char *, char *, scf_type_t, char *); extern int autofs_smf_set_prop(char *, char *, char *, scf_type_t, char *); extern int autofs_smf_get_prop(char *, char *, char *, scf_type_t, char *, int *); extern void fs_smf_fini(fs_smfhandle_t *); extern boolean_t string_to_boolean(const char *); #ifdef __cplusplus } #endif #endif /* _SMFCFG_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "thrpool.h" extern int _nfssys(int, void *); /* * Thread to call into the kernel and do work on behalf of NFS. */ static void * svcstart(void *arg) { int id = (int)arg; /* * Create a kernel worker thread to service * new incoming requests on a pool. */ _nfssys(SVCPOOL_RUN, &id); /* * Returned from the kernel, this thread's work is done, * and it should exit. For new incoming requests, * svcblock() will spawn another worker thread by * calling svcstart() again. */ thr_exit(NULL); return (NULL); } static void * svc_rdma_creator(void *arg) { struct rdma_svc_args *rsap = (struct rdma_svc_args *)arg; if (_nfssys(RDMA_SVC_INIT, rsap) < 0) { if (errno != ENODEV) { (void) syslog(LOG_INFO, "RDMA transport startup " "failed with %m"); } } free(rsap); thr_exit(NULL); return (NULL); } /* * User-space "creator" thread. This thread blocks in the kernel * until new worker threads need to be created for the service * pool. On return to userspace, if there is no error, create a * new thread for the service pool. */ static void * svcblock(void *arg) { int id = (int)arg; /* CONSTCOND */ while (1) { thread_t tid; /* * Call into the kernel, and hang out there * until a thread needs to be created. */ if (_nfssys(SVCPOOL_WAIT, &id) < 0) { if (errno == ECANCELED || errno == EINTR || errno == EBUSY) /* * If we get back ECANCELED or EINTR, * the service pool is exiting, and we * may as well clean up this thread. If * EBUSY is returned, there's already a * thread looping on this pool, so we * should give up. */ break; else continue; } /* * User portion of the thread does no real work since * the svcpool threads actually spend their entire * lives in the kernel. So, user portion of the thread * should have the smallest stack possible. */ (void) thr_create(NULL, THR_MIN_STACK, svcstart, (void *)id, THR_BOUND | THR_DETACHED, &tid); } thr_exit(NULL); return (NULL); } void svcsetprio(void) { pcinfo_t pcinfo; pri_t maxupri; /* * By default, all threads should be part of the FX scheduler * class. As nfsd/lockd server threads used to be part of the * kernel, they're used to being scheduled in the SYS class. * Userland threads shouldn't be in SYS, but they can be given a * higher priority by default. This change still renders nfsd/lockd * managable by an admin by utilizing commands to change scheduling * manually, or by using resource management tools such as pools * to associate them with a different scheduling class and segregate * the workload. * * We set the threads' priority to the upper bound for priorities * in FX. This should be 60, but since the desired action is to * make nfsd/lockd more important than TS threads, we bow to the * system's knowledge rather than setting it manually. Furthermore, * since the SYS class doesn't timeslice, use an "infinite" quantum. * If anything fails, just log the failure and let the daemon * default to TS. * * The change of scheduling class is expected to fail in a non-global * zone, so we avoid worrying the zone administrator unnecessarily. */ (void) strcpy(pcinfo.pc_clname, "FX"); if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) != -1) { maxupri = ((fxinfo_t *)pcinfo.pc_clinfo)->fx_maxupri; if (priocntl(P_LWPID, P_MYID, PC_SETXPARMS, "FX", FX_KY_UPRILIM, maxupri, FX_KY_UPRI, maxupri, FX_KY_TQNSECS, FX_TQINF, NULL) != 0 && getzoneid() == GLOBAL_ZONEID) (void) syslog(LOG_ERR, "Unable to use FX scheduler: " "%m. Using system default scheduler."); } else (void) syslog(LOG_ERR, "Unable to determine parameters " "for FX scheduler. Using system default scheduler."); } int svcrdma(int id, int versmin, int versmax, int delegation) { thread_t tid; struct rdma_svc_args *rsa; rsa = (struct rdma_svc_args *)malloc(sizeof (struct rdma_svc_args)); rsa->poolid = (uint32_t)id; rsa->netid = NULL; rsa->nfs_versmin = versmin; rsa->nfs_versmax = versmax; rsa->delegation = delegation; /* * Create a thread to handle RDMA start and stop. */ if (thr_create(NULL, THR_MIN_STACK * 2, svc_rdma_creator, (void *)rsa, THR_BOUND | THR_DETACHED, &tid)) return (1); return (0); } int svcwait(int id) { thread_t tid; /* * Create a bound thread to wait for kernel LWPs that * need to be created. This thread also has little need * of stackspace, so should be created with that in mind. */ if (thr_create(NULL, THR_MIN_STACK * 2, svcblock, (void *)id, THR_BOUND | THR_DETACHED, &tid)) return (1); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _THRPOOL_H #define _THRPOOL_H #ifdef __cplusplus extern "C" { #endif int svcwait(int id); void svcsetprio(void); int svcrdma(int, int, int, int); #ifdef __cplusplus } #endif #endif /* _THRPOOL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1996-1999, 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ const WNL_PORT = 2049; const WNL_MAXDATA = 8192; const WNL_MAXNAMLEN = 255; const WNL_FHSIZE = 32; const WNL_FIFO_DEV = -1; /* size kludge for named pipes */ /* * Indicator for native path semantics. */ const WNL_NATIVEPATH = 0x80; /* * Indicator for security negotiation. */ const WNL_SEC_NEGO = 0x81; /* * File types */ const WNLMODE_FMT = 0170000; /* type of file */ const WNLMODE_DIR = 0040000; /* directory */ const WNLMODE_CHR = 0020000; /* character special */ const WNLMODE_BLK = 0060000; /* block special */ const WNLMODE_REG = 0100000; /* regular */ const WNLMODE_LNK = 0120000; /* symbolic link */ const WNLMODE_SOCK = 0140000; /* socket */ const WNLMODE_FIFO = 0010000; /* fifo */ /* * Error status */ enum wnl_stat { WNL_OK= 0, /* no error */ WNLERR_PERM=1, /* Not owner */ WNLERR_NOENT=2, /* No such file or directory */ WNLERR_IO=5, /* I/O error */ WNLERR_NXIO=6, /* No such device or address */ WNLERR_ACCES=13, /* Permission denied */ WNLERR_EXIST=17, /* File exists */ WNLERR_XDEV=18, /* Cross-device link */ WNLERR_NODEV=19, /* No such device */ WNLERR_NOTDIR=20, /* Not a directory*/ WNLERR_ISDIR=21, /* Is a directory */ WNLERR_INVAL=22, /* Invalid argument */ WNLERR_FBIG=27, /* File too large */ WNLERR_NOSPC=28, /* No space left on device */ WNLERR_ROFS=30, /* Read-only file system */ WNLERR_OPNOTSUPP=45, /* Operation not supported */ WNLERR_NAMETOOLONG=63, /* File name too long */ WNLERR_NOTEMPTY=66, /* Directory not empty */ WNLERR_DQUOT=69, /* Disc quota exceeded */ WNLERR_STALE=70, /* Stale WNL file handle */ WNLERR_REMOTE=71, /* Object is remote */ WNLERR_WFLUSH=72 /* write cache flushed */ }; /* * File types */ enum wnl_ftype { WNL_NON = 0, /* non-file */ WNL_REG = 1, /* regular file */ WNL_DIR = 2, /* directory */ WNL_BLK = 3, /* block special */ WNL_CHR = 4, /* character special */ WNL_LNK = 5, /* symbolic link */ WNL_SOCK = 6, /* unix domain sockets */ WNL_BAD = 7, /* unused */ WNL_FIFO = 8 /* named pipe */ }; /* * File access handle */ struct wnl_fh { opaque data[WNL_FHSIZE]; }; /* * Timeval */ struct wnl_time { unsigned seconds; unsigned useconds; }; /* * File attributes */ struct wnl_fattr { wnl_ftype type; /* file type */ unsigned mode; /* protection mode bits */ unsigned nlink; /* # hard links */ unsigned uid; /* owner user id */ unsigned gid; /* owner group id */ unsigned size; /* file size in bytes */ unsigned blocksize; /* prefered block size */ unsigned rdev; /* special device # */ unsigned blocks; /* Kb of disk used by file */ unsigned fsid; /* device # */ unsigned fileid; /* inode # */ wnl_time atime; /* time of last access */ wnl_time mtime; /* time of last modification */ wnl_time ctime; /* time of last change */ }; typedef string wnl_filename; /* * Arguments for directory operations */ struct wnl_diropargs { wnl_fh dir; /* directory file handle */ wnl_filename name; /* name (up to WNL_MAXNAMLEN bytes) */ }; struct wnl_diropokres { wnl_fh file; wnl_fattr attributes; }; /* * Results from directory operation */ union wnl_diropres switch (wnl_stat status) { case WNL_OK: wnl_diropokres wnl_diropres; default: void; }; /* * Version 3 declarations and definitions. */ /* * Sizes */ const WNL3_FHSIZE = 64; /* * Basic data types */ typedef unsigned hyper wnl_uint64; typedef hyper wnl_int64; typedef unsigned int wnl_uint32; typedef string wnl_filename3<>; typedef wnl_uint64 wnl_fileid3; typedef wnl_uint32 wnl_uid3; typedef wnl_uint32 wnl_gid3; typedef wnl_uint64 wnl_size3; typedef wnl_uint32 wnl_mode3; /* * Error status */ enum wnl_stat3 { WNL3_OK = 0, WNL3ERR_PERM = 1, WNL3ERR_NOENT = 2, WNL3ERR_IO = 5, WNL3ERR_NXIO = 6, WNL3ERR_ACCES = 13, WNL3ERR_EXIST = 17, WNL3ERR_XDEV = 18, WNL3ERR_NODEV = 19, WNL3ERR_NOTDIR = 20, WNL3ERR_ISDIR = 21, WNL3ERR_INVAL = 22, WNL3ERR_FBIG = 27, WNL3ERR_NOSPC = 28, WNL3ERR_ROFS = 30, WNL3ERR_MLINK = 31, WNL3ERR_NAMETOOLONG = 63, WNL3ERR_NOTEMPTY = 66, WNL3ERR_DQUOT = 69, WNL3ERR_STALE = 70, WNL3ERR_REMOTE = 71, WNL3ERR_BADHANDLE = 10001, WNL3ERR_NOT_SYNC = 10002, WNL3ERR_BAD_COOKIE = 10003, WNL3ERR_NOTSUPP = 10004, WNL3ERR_TOOSMALL = 10005, WNL3ERR_SERVERFAULT = 10006, WNL3ERR_BADTYPE = 10007, WNL3ERR_JUKEBOX = 10008 }; /* * File types */ enum wnl_ftype3 { WNL_3REG = 1, WNL_3DIR = 2, WNL_3BLK = 3, WNL_3CHR = 4, WNL_3LNK = 5, WNL_3SOCK = 6, WNL_3FIFO = 7 }; struct wnl_specdata3 { wnl_uint32 specdata1; wnl_uint32 specdata2; }; /* * File access handle */ struct wnl_fh3 { opaque data; }; /* * Timeval */ struct wnl_time3 { wnl_uint32 seconds; wnl_uint32 nseconds; }; /* * File attributes */ struct wnl_fattr3 { wnl_ftype3 type; wnl_mode3 mode; wnl_uint32 nlink; wnl_uid3 uid; wnl_gid3 gid; wnl_size3 size; wnl_size3 used; wnl_specdata3 rdev; wnl_uint64 fsid; wnl_fileid3 fileid; wnl_time3 atime; wnl_time3 mtime; wnl_time3 ctime; }; /* * File attributes */ union wnl_post_op_attr switch (bool attributes_follow) { case TRUE: wnl_fattr3 attributes; case FALSE: void; }; union wln_post_op_fh3 switch (bool handle_follows) { case TRUE: wnl_fh3 handle; case FALSE: void; }; struct wnl_diropargs3 { wnl_fh3 dir; wnl_filename3 name; }; /* * LOOKUP: Lookup wnl_filename */ struct WNL_LOOKUP3args { wnl_diropargs3 what; }; struct WNL_LOOKUP3resok { wnl_fh3 object; wnl_post_op_attr obj_attributes; wnl_post_op_attr dir_attributes; }; struct WNL_LOOKUP3resfail { wnl_post_op_attr dir_attributes; }; union WNL_LOOKUP3res switch (wnl_stat3 status) { case WNL3_OK: WNL_LOOKUP3resok res_ok; default: WNL_LOOKUP3resfail res_fail; }; const MAX_FLAVORS = 128; struct snego_t { int cnt; int array[MAX_FLAVORS]; }; enum snego_stat { /* default flavor invalid and a flavor has been negotiated */ SNEGO_SUCCESS = 0, /* default flavor valid, no need to negotiate flavors */ SNEGO_DEF_VALID = 1, /* array size too small */ SNEGO_ARRAY_TOO_SMALL = 2, SNEGO_FAILURE = 3 }; /* * Remote file service routines */ program WNL_PROGRAM { version WNL_V2 { void WNLPROC_NULL(void) = 0; wnl_diropres WNLPROC_LOOKUP(wnl_diropargs) = 4; } = 2; version WNL_V3 { void WNLPROC3_NULL(void) = 0; WNL_LOOKUP3res WNLPROC3_LOOKUP(WNL_LOOKUP3args) = 3; } = 3; version WNL_V4 { void WNLPROC4_NULL(void) = 0; } = 4; } = 100003;