/* * Copyright (c) 2000, Boris Popov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Boris Popov. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: ctx.c,v 1.32.70.2 2005/06/02 00:55:40 lindak Exp $ */ /* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. */ #include <sys/param.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/mount.h> #include <sys/types.h> #include <sys/byteorder.h> #include <fcntl.h> #include <ctype.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <strings.h> #include <stdlib.h> #include <pwd.h> #include <grp.h> #include <unistd.h> #include <libintl.h> #include <assert.h> #include <nss_dbdefs.h> #include <cflib.h> #include <netsmb/smb_lib.h> #include <netsmb/netbios.h> #include <netsmb/nb_lib.h> #include <netsmb/smb_dev.h> #include "charsets.h" #include "spnego.h" #include "derparse.h" #include "private.h" #include "ntlm.h" #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif struct nv { char *name; int value; }; /* These two may be set by commands. */ int smb_debug, smb_verbose; /* * Was: STDPARAM_OPT - see smb_ctx_scan_argv, smb_ctx_opt */ const char smbutil_std_opts[] = "ABCD:E:I:L:M:NO:P:U:R:S:T:W:"; /* * Give the RPC library a callback hook that will be * called whenever we destroy or reinit an smb_ctx_t. * The name rpc_cleanup_smbctx() is legacy, and was * originally a direct call into the RPC code. */ static smb_ctx_close_hook_t close_hook; static void rpc_cleanup_smbctx(struct smb_ctx *ctx) { if (close_hook) (*close_hook)(ctx); } void smb_ctx_set_close_hook(smb_ctx_close_hook_t hook) { close_hook = hook; } void dump_ctx_flags(int flags) { printf(" Flags: "); if (flags == 0) printf("0"); if (flags & SMBCF_NOPWD) printf("NOPWD "); if (flags & SMBCF_SRIGHTS) printf("SRIGHTS "); if (flags & SMBCF_LOCALE) printf("LOCALE "); if (flags & SMBCF_CMD_DOM) printf("CMD_DOM "); if (flags & SMBCF_CMD_USR) printf("CMD_USR "); if (flags & SMBCF_CMD_PW) printf("CMD_PW "); if (flags & SMBCF_RESOLVED) printf("RESOLVED "); if (flags & SMBCF_KCBAD) printf("KCBAD "); if (flags & SMBCF_KCFOUND) printf("KCFOUND "); if (flags & SMBCF_BROWSEOK) printf("BROWSEOK "); if (flags & SMBCF_AUTHREQ) printf("AUTHREQ "); if (flags & SMBCF_KCSAVE) printf("KCSAVE "); if (flags & SMBCF_XXX) printf("XXX "); if (flags & SMBCF_SSNACTIVE) printf("SSNACTIVE "); if (flags & SMBCF_KCDOMAIN) printf("KCDOMAIN "); printf("\n"); } void dump_iod_ssn(smb_iod_ssn_t *is) { static const char zeros[NTLM_HASH_SZ] = {0}; struct smbioc_ossn *ssn = &is->iod_ossn; printf(" ct_srvname=\"%s\", ", ssn->ssn_srvname); dump_sockaddr(&ssn->ssn_srvaddr.sa); printf(" dom=\"%s\", user=\"%s\"\n", ssn->ssn_domain, ssn->ssn_user); printf(" ct_vopt=0x%x, ct_owner=%d\n", ssn->ssn_vopt, ssn->ssn_owner); printf(" ct_authflags=0x%x\n", is->iod_authflags); printf(" ct_nthash:"); if (bcmp(zeros, &is->iod_nthash, NTLM_HASH_SZ)) smb_hexdump(&is->iod_nthash, NTLM_HASH_SZ); else printf(" {0}\n"); printf(" ct_lmhash:"); if (bcmp(zeros, &is->iod_lmhash, NTLM_HASH_SZ)) smb_hexdump(&is->iod_lmhash, NTLM_HASH_SZ); else printf(" {0}\n"); } void dump_ctx(char *where, struct smb_ctx *ctx) { printf("context %s:\n", where); dump_ctx_flags(ctx->ct_flags); if (ctx->ct_locname) printf(" localname=\"%s\"", ctx->ct_locname); else printf(" localname=NULL"); if (ctx->ct_fullserver) printf(" fullserver=\"%s\"", ctx->ct_fullserver); else printf(" fullserver=NULL"); if (ctx->ct_srvaddr_s) printf(" srvaddr_s=\"%s\"\n", ctx->ct_srvaddr_s); else printf(" srvaddr_s=NULL\n"); if (ctx->ct_addrinfo) dump_addrinfo(ctx->ct_addrinfo); else printf(" ct_addrinfo = NULL\n"); dump_iod_ssn(&ctx->ct_iod_ssn); printf(" share_name=\"%s\", share_type=%d\n", ctx->ct_origshare ? ctx->ct_origshare : "", ctx->ct_shtype_req); /* dump_iod_work()? */ } int smb_ctx_alloc(struct smb_ctx **ctx_pp) { smb_ctx_t *ctx; int err; ctx = malloc(sizeof (*ctx)); if (ctx == NULL) return (ENOMEM); err = smb_ctx_init(ctx); if (err != 0) { free(ctx); return (err); } *ctx_pp = ctx; return (0); } /* * Initialize an smb_ctx struct (defaults) */ int smb_ctx_init(struct smb_ctx *ctx) { char pwbuf[NSS_BUFLEN_PASSWD]; struct passwd pw; int error = 0; bzero(ctx, sizeof (*ctx)); error = nb_ctx_create(&ctx->ct_nb); if (error) return (error); ctx->ct_dev_fd = -1; ctx->ct_door_fd = -1; ctx->ct_tran_fd = -1; ctx->ct_parsedlevel = SMBL_NONE; ctx->ct_minlevel = SMBL_NONE; ctx->ct_maxlevel = SMBL_PATH; /* Fill in defaults */ ctx->ct_vopt = SMBVOPT_EXT_SEC; ctx->ct_owner = SMBM_ANY_OWNER; ctx->ct_authflags = SMB_AT_DEFAULT; ctx->ct_minauth = SMB_AT_DEFAULT; error = nb_ctx_setscope(ctx->ct_nb, ""); if (error) return (error); /* * if the user name is not specified some other way, * use the current user name (built-in default) */ if (getpwuid_r(getuid(), &pw, pwbuf, sizeof (pwbuf)) != NULL) { error = smb_ctx_setuser(ctx, pw.pw_name, 0); if (error) return (error); ctx->ct_home = strdup(pw.pw_name); if (ctx->ct_home == NULL) return (ENOMEM); } /* * Set a built-in default domain (workgroup). * Using the Windows/NT default for now. */ error = smb_ctx_setdomain(ctx, "WORKGROUP", 0); if (error) return (error); return (error); } /* * "Scan" the command line args to find the server name, * user name, and share name, as needed. We need these * before reading the RC files and/or sharectl values. * * The sequence for getting all the members filled in * has some tricky aspects. Here's how it works: * * The search order for options is as follows: * command line options * values parsed from UNC path (cmd) * values from RC file (per-user) * values from SMF (system-wide) * built-in defaults * * Normally, one would simply get all the values starting with * the bottom of the above list and working to the top, and * overwriting values as you go. But we need an exception. * * In this function, we parse the UNC path and command line options, * because we need (at least) the server name when we're getting the * SMF and RC file values. However, values we get from the command * should not be overwritten by SMF or RC file parsing, so we mark * values from the command as "from CMD" and the RC file parser * leaves in place any values so marked. See: SMBCF_CMD_* * * The semantics of these flags are: "This value came from the * current command instance, not from sources that may apply to * multiple commands." (Different from the old "FROMUSR" flag.) * * Note that smb_ctx_opt() is called later to handle the * remaining options, which should be ignored here. * The (magic) leading ":" in cf_getopt() makes it * ignore options not in the options string. */ int smb_ctx_scan_argv(struct smb_ctx *ctx, int argc, char **argv, int minlevel, int maxlevel, int sharetype) { int ind, opt, error = 0; int aflg = 0, uflg = 0; const char *arg; /* * Parse options, if any. Values from here too * are marked as "from CMD". */ if (argv == NULL) return (0); ctx->ct_minlevel = minlevel; ctx->ct_maxlevel = maxlevel; ctx->ct_shtype_req = sharetype; cf_opt_lock(); /* Careful: no return/goto before cf_opt_unlock! */ while (error == 0) { opt = cf_getopt(argc, argv, STDPARAM_OPT); if (opt == -1) break; arg = cf_optarg; /* NB: handle most in smb_ctx_opt */ switch (opt) { case 'A': aflg = 1; error = smb_ctx_setuser(ctx, "", TRUE); ctx->ct_flags |= SMBCF_NOPWD; break; case 'U': uflg = 1; error = smb_ctx_setuser(ctx, arg, TRUE); break; default: DPRINT("skip opt=%c", opt); break; } } ind = cf_optind; arg = argv[ind]; cf_optind = cf_optreset = 1; cf_opt_unlock(); if (error) return (error); if (aflg && uflg) { printf(gettext("-A and -U flags are exclusive.\n")); return (EINVAL); } /* * Parse the UNC path. Values from here are * marked as "from CMD". */ for (; ind < argc; ind++) { arg = argv[ind]; if (strncmp(arg, "//", 2) != 0) continue; error = smb_ctx_parseunc(ctx, arg, minlevel, maxlevel, sharetype, &arg); if (error) return (error); break; } return (error); } void smb_ctx_free(smb_ctx_t *ctx) { smb_ctx_done(ctx); free(ctx); } void smb_ctx_done(struct smb_ctx *ctx) { rpc_cleanup_smbctx(ctx); if (ctx->ct_dev_fd != -1) { close(ctx->ct_dev_fd); ctx->ct_dev_fd = -1; } if (ctx->ct_door_fd != -1) { close(ctx->ct_door_fd); ctx->ct_door_fd = -1; } if (ctx->ct_tran_fd != -1) { close(ctx->ct_tran_fd); ctx->ct_tran_fd = -1; } if (ctx->ct_srvaddr_s) { free(ctx->ct_srvaddr_s); ctx->ct_srvaddr_s = NULL; } if (ctx->ct_nb) { nb_ctx_done(ctx->ct_nb); ctx->ct_nb = NULL; } if (ctx->ct_locname) { free(ctx->ct_locname); ctx->ct_locname = NULL; } if (ctx->ct_origshare) { free(ctx->ct_origshare); ctx->ct_origshare = NULL; } if (ctx->ct_fullserver) { free(ctx->ct_fullserver); ctx->ct_fullserver = NULL; } if (ctx->ct_addrinfo) { freeaddrinfo(ctx->ct_addrinfo); ctx->ct_addrinfo = NULL; } if (ctx->ct_home) free(ctx->ct_home); if (ctx->ct_srv_OS) { free(ctx->ct_srv_OS); ctx->ct_srv_OS = NULL; } if (ctx->ct_srv_LM) { free(ctx->ct_srv_LM); ctx->ct_srv_LM = NULL; } if (ctx->ct_mackey) { free(ctx->ct_mackey); ctx->ct_mackey = NULL; } } static int getsubstring(const char *p, uchar_t sep, char *dest, int maxlen, const char **next) { int len; maxlen--; for (len = 0; len < maxlen && *p != sep; p++, len++, dest++) { if (*p == 0) return (EINVAL); *dest = *p; } *dest = 0; *next = *p ? p + 1 : p; return (0); } /* * Parse the UNC path. Here we expect something like * "//[workgroup;][user[:password]@]host[/share[/path]]" * See http://ietf.org/internet-drafts/draft-crhertel-smb-url-07.txt * Values found here are marked as "from CMD". */ int smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int minlevel, int maxlevel, int sharetype, const char **next) { const char *p = unc; char *p1, *colon; char tmp[1024]; int error; /* * This may be called outside of _scan_argv, * so make sure these get initialized. */ ctx->ct_minlevel = minlevel; ctx->ct_maxlevel = maxlevel; ctx->ct_shtype_req = sharetype; ctx->ct_parsedlevel = SMBL_NONE; if (*p++ != '/' || *p++ != '/') { smb_error(dgettext(TEXT_DOMAIN, "UNC should start with '//'"), 0); error = EINVAL; goto out; } p1 = tmp; error = getsubstring(p, ';', p1, sizeof (tmp), &p); if (!error) { if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty workgroup name"), 0); error = EINVAL; goto out; } error = smb_ctx_setdomain(ctx, unpercent(tmp), TRUE); if (error) goto out; } colon = (char *)p; error = getsubstring(p, '@', p1, sizeof (tmp), &p); if (!error) { if (ctx->ct_maxlevel < SMBL_VC) { smb_error(dgettext(TEXT_DOMAIN, "no user name required"), 0); error = EINVAL; goto out; } p1 = strchr(tmp, ':'); if (p1) { colon += p1 - tmp; *p1++ = (char)0; error = smb_ctx_setpassword(ctx, unpercent(p1), TRUE); if (error) goto out; if (p - colon > 2) memset(colon+1, '*', p - colon - 2); } p1 = tmp; if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty user name"), 0); error = EINVAL; goto out; } error = smb_ctx_setuser(ctx, unpercent(tmp), TRUE); if (error) goto out; ctx->ct_parsedlevel = SMBL_VC; } error = getsubstring(p, '/', p1, sizeof (tmp), &p); if (error) { error = getsubstring(p, '\0', p1, sizeof (tmp), &p); if (error) { smb_error(dgettext(TEXT_DOMAIN, "no server name found"), 0); goto out; } } if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty server name"), 0); error = EINVAL; goto out; } /* * Save ct_fullserver without case conversion. */ if (strchr(tmp, '%')) (void) unpercent(tmp); error = smb_ctx_setfullserver(ctx, tmp); if (error) goto out; #ifdef SMB_ST_NONE if (sharetype == SMB_ST_NONE) { if (next) *next = p; error = 0; goto out; } #endif if (*p != 0 && ctx->ct_maxlevel < SMBL_SHARE) { smb_error(dgettext(TEXT_DOMAIN, "no share name required"), 0); error = EINVAL; goto out; } error = getsubstring(p, '/', p1, sizeof (tmp), &p); if (error) { error = getsubstring(p, '\0', p1, sizeof (tmp), &p); if (error) { smb_error(dgettext(TEXT_DOMAIN, "unexpected end of line"), 0); goto out; } } if (*p1 == 0 && ctx->ct_minlevel >= SMBL_SHARE && !(ctx->ct_flags & SMBCF_BROWSEOK)) { smb_error(dgettext(TEXT_DOMAIN, "empty share name"), 0); error = EINVAL; goto out; } if (next) *next = p; if (*p1 == 0) { error = 0; goto out; } error = smb_ctx_setshare(ctx, unpercent(p1), sharetype); out: if (error == 0 && smb_debug > 0) dump_ctx("after smb_ctx_parseunc", ctx); return (error); } #ifdef KICONV_SUPPORT int smb_ctx_setcharset(struct smb_ctx *ctx, const char *arg) { char *cp, *servercs, *localcs; int cslen = sizeof (ctx->ct_ssn.ioc_localcs); int scslen, lcslen, error; cp = strchr(arg, ':'); lcslen = cp ? (cp - arg) : 0; if (lcslen == 0 || lcslen >= cslen) { smb_error(dgettext(TEXT_DOMAIN, "invalid local charset specification (%s)"), 0, arg); return (EINVAL); } scslen = (size_t)strlen(++cp); if (scslen == 0 || scslen >= cslen) { smb_error(dgettext(TEXT_DOMAIN, "invalid server charset specification (%s)"), 0, arg); return (EINVAL); } localcs = memcpy(ctx->ct_ssn.ioc_localcs, arg, lcslen); localcs[lcslen] = 0; servercs = strcpy(ctx->ct_ssn.ioc_servercs, cp); error = nls_setrecode(localcs, servercs); if (error == 0) return (0); smb_error(dgettext(TEXT_DOMAIN, "can't initialize iconv support (%s:%s)"), error, localcs, servercs); localcs[0] = 0; servercs[0] = 0; return (error); } #endif /* KICONV_SUPPORT */ int smb_ctx_setauthflags(struct smb_ctx *ctx, int flags) { ctx->ct_authflags = flags; return (0); } int smb_ctx_setfullserver(struct smb_ctx *ctx, const char *name) { char *p = strdup(name); if (p == NULL) return (ENOMEM); if (ctx->ct_fullserver) free(ctx->ct_fullserver); ctx->ct_fullserver = p; return (0); } int smb_ctx_setserver(struct smb_ctx *ctx, const char *name) { strlcpy(ctx->ct_srvname, name, sizeof (ctx->ct_srvname)); return (0); } int smb_ctx_setuser(struct smb_ctx *ctx, const char *name, int from_cmd) { if (strlen(name) >= sizeof (ctx->ct_user)) { smb_error(dgettext(TEXT_DOMAIN, "user name '%s' too long"), 0, name); return (ENAMETOOLONG); } /* * Don't overwrite a value from the command line * with one from anywhere else. */ if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_USR)) return (0); strlcpy(ctx->ct_user, name, sizeof (ctx->ct_user)); /* Mark this as "from the command line". */ if (from_cmd) ctx->ct_flags |= SMBCF_CMD_USR; return (0); } /* * Don't overwrite a domain name from the * command line with one from anywhere else. * See smb_ctx_init() for notes about this. */ int smb_ctx_setdomain(struct smb_ctx *ctx, const char *name, int from_cmd) { if (strlen(name) >= sizeof (ctx->ct_domain)) { smb_error(dgettext(TEXT_DOMAIN, "workgroup name '%s' too long"), 0, name); return (ENAMETOOLONG); } /* * Don't overwrite a value from the command line * with one from anywhere else. */ if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_DOM)) return (0); strlcpy(ctx->ct_domain, name, sizeof (ctx->ct_domain)); /* Mark this as "from the command line". */ if (from_cmd) ctx->ct_flags |= SMBCF_CMD_DOM; return (0); } int smb_ctx_setpassword(struct smb_ctx *ctx, const char *passwd, int from_cmd) { int err; if (passwd == NULL) return (EINVAL); if (strlen(passwd) >= sizeof (ctx->ct_password)) { smb_error(dgettext(TEXT_DOMAIN, "password too long"), 0); return (ENAMETOOLONG); } /* * If called again after comand line parsing, * don't overwrite a value from the command line * with one from any stored config. */ if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_PW)) return (0); memset(ctx->ct_password, 0, sizeof (ctx->ct_password)); if (strncmp(passwd, "$$1", 3) == 0) (void) smb_simpledecrypt(ctx->ct_password, passwd); else strlcpy(ctx->ct_password, passwd, sizeof (ctx->ct_password)); /* * Compute LM hash, NT hash. */ if (ctx->ct_password[0]) { err = ntlm_compute_nt_hash(ctx->ct_nthash, ctx->ct_password); if (err != 0) return (err); err = ntlm_compute_lm_hash(ctx->ct_lmhash, ctx->ct_password); if (err != 0) return (err); } /* Mark this as "from the command line". */ if (from_cmd) ctx->ct_flags |= SMBCF_CMD_PW; return (0); } /* * Use this to set NTLM auth. info (hashes) * when we don't have the password. */ int smb_ctx_setpwhash(smb_ctx_t *ctx, const uchar_t *nthash, const uchar_t *lmhash) { /* Need ct_password to be non-null. */ if (ctx->ct_password[0] == '\0') strlcpy(ctx->ct_password, "$HASH", sizeof (ctx->ct_password)); /* * Compute LM hash, NT hash. */ memcpy(ctx->ct_nthash, nthash, NTLM_HASH_SZ); /* The LM hash is optional */ if (lmhash) { memcpy(ctx->ct_nthash, nthash, NTLM_HASH_SZ); } return (0); } int smb_ctx_setshare(struct smb_ctx *ctx, const char *share, int stype) { if (strlen(share) >= SMBIOC_MAX_NAME) { smb_error(dgettext(TEXT_DOMAIN, "share name '%s' too long"), 0, share); return (ENAMETOOLONG); } if (ctx->ct_origshare) free(ctx->ct_origshare); if ((ctx->ct_origshare = strdup(share)) == NULL) return (ENOMEM); ctx->ct_shtype_req = stype; return (0); } int smb_ctx_setsrvaddr(struct smb_ctx *ctx, const char *addr) { if (addr == NULL || addr[0] == 0) return (EINVAL); if (ctx->ct_srvaddr_s) free(ctx->ct_srvaddr_s); if ((ctx->ct_srvaddr_s = strdup(addr)) == NULL) return (ENOMEM); return (0); } /* * API for library caller to set signing enabled, required * Note: if not enable, ignore require */ int smb_ctx_setsigning(struct smb_ctx *ctx, int enable, int require) { ctx->ct_vopt &= ~SMBVOPT_SIGNING_MASK; if (enable) { ctx->ct_vopt |= SMBVOPT_SIGNING_ENABLED; if (require) ctx->ct_vopt |= SMBVOPT_SIGNING_REQUIRED; } return (0); } static int smb_parse_owner(char *pair, uid_t *uid, gid_t *gid) { struct group gr; struct passwd pw; char buf[NSS_BUFLEN_PASSWD]; char *cp; cp = strchr(pair, ':'); if (cp) { *cp++ = '\0'; if (*cp && gid) { if (getgrnam_r(cp, &gr, buf, sizeof (buf)) != NULL) { *gid = gr.gr_gid; } else smb_error(dgettext(TEXT_DOMAIN, "Invalid group name %s, ignored"), 0, cp); } } if (*pair) { if (getpwnam_r(pair, &pw, buf, sizeof (buf)) != NULL) { *uid = pw.pw_uid; } else smb_error(dgettext(TEXT_DOMAIN, "Invalid user name %s, ignored"), 0, pair); } return (0); } /* * Suport a securty options arg, i.e. -S noext,lm,ntlm * for testing various type of authenticators. */ static struct nv sectype_table[] = { /* noext - handled below */ { "anon", SMB_AT_ANON }, { "lm", SMB_AT_LM1 }, { "ntlm", SMB_AT_NTLM1 }, { "ntlm2", SMB_AT_NTLM2 }, { "krb5", SMB_AT_KRB5 }, { NULL, 0 }, }; int smb_parse_secopts(struct smb_ctx *ctx, const char *arg) { const char *sep = ":;,"; const char *p = arg; struct nv *nv; int nlen, tlen; int authflags = 0; for (;;) { /* skip separators */ tlen = strspn(p, sep); p += tlen; nlen = strcspn(p, sep); if (nlen == 0) break; if (nlen == 5 && 0 == strncmp(p, "noext", nlen)) { /* Don't offer extended security. */ ctx->ct_vopt &= ~SMBVOPT_EXT_SEC; p += nlen; continue; } /* This is rarely called, so not optimized. */ for (nv = sectype_table; nv->name; nv++) { tlen = strlen(nv->name); if (tlen == nlen && 0 == strncmp(p, nv->name, tlen)) break; } if (nv->name == NULL) { smb_error(dgettext(TEXT_DOMAIN, "%s: invalid security options"), 0, p); return (EINVAL); } authflags |= nv->value; p += nlen; } if (authflags) ctx->ct_authflags = authflags; return (0); } /* * Commands use this with getopt. See: * STDPARAM_OPT, STDPARAM_ARGS * Called after smb_ctx_readrc(). */ int smb_ctx_opt(struct smb_ctx *ctx, int opt, const char *arg) { int error = 0; char *p, *cp; char tmp[1024]; switch (opt) { case 'A': case 'U': /* Handled in smb_ctx_init() */ break; case 'I': error = smb_ctx_setsrvaddr(ctx, arg); break; case 'M': /* share connect rights - ignored */ ctx->ct_flags |= SMBCF_SRIGHTS; break; case 'N': ctx->ct_flags |= SMBCF_NOPWD; break; case 'O': p = strdup(arg); cp = strchr(p, '/'); if (cp) *cp = '\0'; error = smb_parse_owner(cp, &ctx->ct_owner, NULL); free(p); break; case 'P': /* ctx->ct_vopt |= SMBCOPT_PERMANENT; */ break; case 'R': /* retry count - ignored */ break; case 'S': /* Security options (undocumented, just for tests) */ error = smb_parse_secopts(ctx, arg); break; case 'T': /* timeout - ignored */ break; case 'D': /* domain */ case 'W': /* workgroup (legacy alias) */ error = smb_ctx_setdomain(ctx, tmp, TRUE); break; } return (error); } /* * Original code injected iconv tables into the kernel. * Not sure if we'll need this or not... REVISIT */ #ifdef KICONV_SUPPORT static int smb_addiconvtbl(const char *to, const char *from, const uchar_t *tbl) { int error = 0; error = kiconv_add_xlat_table(to, from, tbl); if (error && error != EEXIST) { smb_error(dgettext(TEXT_DOMAIN, "can not setup kernel iconv table (%s:%s)"), error, from, to); return (error); } return (error); } #endif /* KICONV_SUPPORT */ /* * Verify context info. before connect operation(s), * lookup specified server and try to fill all forgotten fields. * Legacy name used by commands. */ int smb_ctx_resolve(struct smb_ctx *ctx) { struct smbioc_ossn *ssn = &ctx->ct_ssn; int error = 0; #ifdef KICONV_SUPPORT uchar_t cstbl[256]; uint_t i; #endif if (smb_debug) dump_ctx("before smb_ctx_resolve", ctx); ctx->ct_flags &= ~SMBCF_RESOLVED; if (ctx->ct_fullserver == NULL) { smb_error(dgettext(TEXT_DOMAIN, "no server name specified"), 0); return (EINVAL); } if (ctx->ct_minlevel >= SMBL_SHARE && ctx->ct_origshare == NULL) { smb_error(dgettext(TEXT_DOMAIN, "no share name specified for %s@%s"), 0, ssn->ssn_user, ctx->ct_fullserver); return (EINVAL); } error = nb_ctx_resolve(ctx->ct_nb); if (error) return (error); #ifdef KICONV_SUPPORT if (ssn->ioc_localcs[0] == 0) strcpy(ssn->ioc_localcs, "default"); /* XXX: locale name ? */ error = smb_addiconvtbl("tolower", ssn->ioc_localcs, nls_lower); if (error) return (error); error = smb_addiconvtbl("toupper", ssn->ioc_localcs, nls_upper); if (error) return (error); if (ssn->ioc_servercs[0] != 0) { for (i = 0; i < sizeof (cstbl); i++) cstbl[i] = i; nls_mem_toext(cstbl, cstbl, sizeof (cstbl)); error = smb_addiconvtbl(ssn->ioc_servercs, ssn->ioc_localcs, cstbl); if (error) return (error); for (i = 0; i < sizeof (cstbl); i++) cstbl[i] = i; nls_mem_toloc(cstbl, cstbl, sizeof (cstbl)); error = smb_addiconvtbl(ssn->ioc_localcs, ssn->ioc_servercs, cstbl); if (error) return (error); } #endif /* KICONV_SUPPORT */ /* * Lookup the IP address and fill in ct_addrinfo. * * Note: smb_ctx_getaddr() returns a EAI_xxx * error value like getaddrinfo(3), but this * function needs to return an errno value. */ error = smb_ctx_getaddr(ctx); if (error) { const char *ais = gai_strerror(error); smb_error(dgettext(TEXT_DOMAIN, "can't resolve name\"%s\", %s"), 0, ctx->ct_fullserver, ais); return (ENODATA); } assert(ctx->ct_addrinfo != NULL); /* * If we have a user name but no password, * check for a keychain entry. * XXX: Only for auth NTLM? */ if (ctx->ct_user[0] == '\0') { /* * No user name (anonymous session). * The minauth checks do not apply. */ ctx->ct_authflags = SMB_AT_ANON; } else { /* * Have a user name. * If we don't have a p/w yet, * try the keychain. */ if (ctx->ct_password[0] == '\0') (void) smb_get_keychain(ctx); /* * Mask out disallowed auth types. */ ctx->ct_authflags &= ctx->ct_minauth; } if (ctx->ct_authflags == 0) { smb_error(dgettext(TEXT_DOMAIN, "no valid auth. types"), 0); return (ENOTSUP); } ctx->ct_flags |= SMBCF_RESOLVED; if (smb_debug) dump_ctx("after smb_ctx_resolve", ctx); return (0); } int smb_open_driver() { int err, fd; uint32_t version; fd = open("/dev/"NSMB_NAME, O_RDWR); if (fd < 0) { err = errno; smb_error(dgettext(TEXT_DOMAIN, "failed to open driver"), err); return (-1); } /* * Check the driver version (paranoia) * Do this BEFORE any other ioctl calls. */ if (ioctl(fd, SMBIOC_GETVERS, &version) < 0) version = 0; if (version != NSMB_VERSION) { smb_error(dgettext(TEXT_DOMAIN, "incorrect driver version"), 0); close(fd); return (-1); } /* This handle controls per-process resources. */ (void) fcntl(fd, F_SETFD, FD_CLOEXEC); return (fd); } int smb_ctx_gethandle(struct smb_ctx *ctx) { int fd; if (ctx->ct_dev_fd != -1) { rpc_cleanup_smbctx(ctx); close(ctx->ct_dev_fd); ctx->ct_dev_fd = -1; ctx->ct_flags &= ~SMBCF_SSNACTIVE; } fd = smb_open_driver(); if (fd < 0) return (ENODEV); ctx->ct_dev_fd = fd; return (0); } /* * Find or create a connection + logon session */ int smb_ctx_get_ssn(struct smb_ctx *ctx) { int err = 0; if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) return (EINVAL); if (ctx->ct_dev_fd < 0) { if ((err = smb_ctx_gethandle(ctx))) return (err); } /* * Check whether the driver already has a VC * we can use. If so, we're done! */ err = smb_ctx_findvc(ctx); if (err == 0) { DPRINT("found an existing VC"); } else { /* * This calls the IOD to create a new session. */ DPRINT("setup a new VC"); err = smb_ctx_newvc(ctx); if (err != 0) return (err); /* * Call findvc again. The new VC sould be * found in the driver this time. */ err = smb_ctx_findvc(ctx); } return (err); } /* * Get the string representation of a share "use" type, * as needed for the "service" in tree connect. */ static const char * smb_use_type_str(smb_use_shtype_t stype) { const char *pp; switch (stype) { default: case USE_WILDCARD: pp = "?????"; break; case USE_DISKDEV: pp = "A:"; break; case USE_SPOOLDEV: pp = "LPT1:"; break; case USE_CHARDEV: pp = "COMM"; break; case USE_IPC: pp = "IPC"; break; } return (pp); } /* * Find or create a tree connection */ int smb_ctx_get_tree(struct smb_ctx *ctx) { smbioc_tcon_t *tcon = NULL; const char *stype; int cmd, err = 0; if (ctx->ct_dev_fd < 0 || ctx->ct_origshare == NULL) { return (EINVAL); } cmd = SMBIOC_TREE_CONNECT; tcon = malloc(sizeof (*tcon)); if (tcon == NULL) return (ENOMEM); bzero(tcon, sizeof (*tcon)); tcon->tc_flags = SMBLK_CREATE; tcon->tc_opt = 0; /* The share name */ strlcpy(tcon->tc_sh.sh_name, ctx->ct_origshare, sizeof (tcon->tc_sh.sh_name)); /* * Share password (unused - no share-level security) * MS-SMB 2.2.6 says this should be null terminated, * and the length includes the null. Did bzero above, * so just set length for the null. */ tcon->tc_sh.sh_pwlen = 1; /* The share "use" type. */ stype = smb_use_type_str(ctx->ct_shtype_req); strlcpy(tcon->tc_sh.sh_type_req, stype, sizeof (tcon->tc_sh.sh_type_req)); /* * Todo: share passwords for share-level security. * * The driver does the actual TCON call. */ if (ioctl(ctx->ct_dev_fd, cmd, tcon) == -1) { err = errno; goto out; } /* * Check the returned share type */ DPRINT("ret. sh_type: \"%s\"", tcon->tc_sh.sh_type_ret); if (ctx->ct_shtype_req != USE_WILDCARD && 0 != strcmp(stype, tcon->tc_sh.sh_type_ret)) { smb_error(dgettext(TEXT_DOMAIN, "%s: incompatible share type"), 0, ctx->ct_origshare); err = EINVAL; } out: if (tcon != NULL) free(tcon); return (err); } /* * Return the hflags2 word for an smb_ctx. */ int smb_ctx_flags2(struct smb_ctx *ctx) { uint16_t flags2; if (ioctl(ctx->ct_dev_fd, SMBIOC_FLAGS2, &flags2) == -1) { smb_error(dgettext(TEXT_DOMAIN, "can't get flags2 for a session"), errno); return (-1); } return (flags2); } /* * Get the transport level session key. * Must already have an active SMB session. */ int smb_ctx_get_ssnkey(struct smb_ctx *ctx, uchar_t *key, size_t len) { if (len < SMBIOC_HASH_SZ) return (EINVAL); if (ioctl(ctx->ct_dev_fd, SMBIOC_GETSSNKEY, key) == -1) return (errno); return (0); } /* * RC file parsing stuff */ static struct nv minauth_table[] = { /* Allowed auth. types */ { "kerberos", SMB_AT_KRB5 }, { "ntlmv2", SMB_AT_KRB5|SMB_AT_NTLM2 }, { "ntlm", SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1 }, { "lm", SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1 }, { "none", SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1| SMB_AT_ANON }, { NULL } }; /* * level values: * 0 - default * 1 - server * 2 - server:user * 3 - server:user:share */ static int smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) { char *p; int error; #ifdef KICONV_SUPPORT if (level > 0) { rc_getstringptr(smb_rc, sname, "charsets", &p); if (p) { error = smb_ctx_setcharset(ctx, p); if (error) smb_error(dgettext(TEXT_DOMAIN, "charset specification in the section '%s' ignored"), error, sname); } } #endif if (level <= 1) { /* Section is: [default] or [server] */ rc_getstringptr(smb_rc, sname, "minauth", &p); if (p) { /* * "minauth" was set in this section; override * the current minimum authentication setting. */ struct nv *nvp; for (nvp = minauth_table; nvp->name; nvp++) if (strcmp(p, nvp->name) == 0) break; if (nvp->name) ctx->ct_minauth = nvp->value; else { /* * Unknown minimum authentication level. */ smb_error(dgettext(TEXT_DOMAIN, "invalid minimum authentication level \"%s\" specified in the section %s"), 0, p, sname); return (EINVAL); } } rc_getstringptr(smb_rc, sname, "signing", &p); if (p) { /* * "signing" was set in this section; override * the current signing settings. Note: * setsigning flags are: enable, require */ if (strcmp(p, "disabled") == 0) { (void) smb_ctx_setsigning(ctx, FALSE, FALSE); } else if (strcmp(p, "enabled") == 0) { (void) smb_ctx_setsigning(ctx, TRUE, FALSE); } else if (strcmp(p, "required") == 0) { (void) smb_ctx_setsigning(ctx, TRUE, TRUE); } else { /* * Unknown "signing" value. */ smb_error(dgettext(TEXT_DOMAIN, "invalid signing policy \"%s\" specified in the section %s"), 0, p, sname); return (EINVAL); } } /* * Domain name. Allow both keywords: * "workgroup", "domain" * * Note: these are NOT marked "from CMD". * See long comment at smb_ctx_init() */ rc_getstringptr(smb_rc, sname, "workgroup", &p); if (p) { error = smb_ctx_setdomain(ctx, p, 0); if (error) smb_error(dgettext(TEXT_DOMAIN, "workgroup specification in the " "section '%s' ignored"), error, sname); } rc_getstringptr(smb_rc, sname, "domain", &p); if (p) { error = smb_ctx_setdomain(ctx, p, 0); if (error) smb_error(dgettext(TEXT_DOMAIN, "domain specification in the " "section '%s' ignored"), error, sname); } rc_getstringptr(smb_rc, sname, "user", &p); if (p) { error = smb_ctx_setuser(ctx, p, 0); if (error) smb_error(dgettext(TEXT_DOMAIN, "user specification in the " "section '%s' ignored"), error, sname); } } if (level == 1) { /* Section is: [server] */ rc_getstringptr(smb_rc, sname, "addr", &p); if (p) { error = smb_ctx_setsrvaddr(ctx, p); if (error) { smb_error(dgettext(TEXT_DOMAIN, "invalid address specified in section %s"), 0, sname); return (error); } } } rc_getstringptr(smb_rc, sname, "password", &p); if (p) { error = smb_ctx_setpassword(ctx, p, 0); if (error) smb_error(dgettext(TEXT_DOMAIN, "password specification in the section '%s' ignored"), error, sname); } return (0); } /* * read rc file as follows: * 0: read [default] section * 1: override with [server] section * 2: override with [server:user] section * 3: override with [server:user:share] section * Since absence of rcfile is not fatal, silently ignore this fact. * smb_rc file should be closed by caller. */ int smb_ctx_readrc(struct smb_ctx *ctx) { char *home; char *sname = NULL; int sname_max; int err = 0; if ((home = getenv("HOME")) == NULL) home = ctx->ct_home; if ((err = smb_open_rcfile(home)) != 0) { DPRINT("smb_open_rcfile, err=%d", err); /* ignore any error here */ return (0); } sname_max = 3 * SMBIOC_MAX_NAME + 4; sname = malloc(sname_max); if (sname == NULL) { err = ENOMEM; goto done; } /* * default parameters (level=0) */ smb_ctx_readrcsection(ctx, "default", 0); nb_ctx_readrcsection(smb_rc, ctx->ct_nb, "default", 0); /* * If we don't have a server name, we can't read any of the * [server...] sections. */ if (ctx->ct_fullserver == NULL) goto done; /* * SERVER parameters. */ smb_ctx_readrcsection(ctx, ctx->ct_fullserver, 1); /* * If we don't have a user name, we can't read any of the * [server:user...] sections. */ if (ctx->ct_user[0] == 0) goto done; /* * SERVER:USER parameters */ snprintf(sname, sname_max, "%s:%s", ctx->ct_fullserver, ctx->ct_user); smb_ctx_readrcsection(ctx, sname, 2); /* * If we don't have a share name, we can't read any of the * [server:user:share] sections. */ if (ctx->ct_origshare == NULL) goto done; /* * SERVER:USER:SHARE parameters */ snprintf(sname, sname_max, "%s:%s:%s", ctx->ct_fullserver, ctx->ct_user, ctx->ct_origshare); smb_ctx_readrcsection(ctx, sname, 3); done: if (sname) free(sname); smb_close_rcfile(); if (smb_debug) dump_ctx("after smb_ctx_readrc", ctx); if (err) DPRINT("err=%d\n", err); return (err); }