/* * 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 $ */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern uid_t real_uid, eff_uid; #define NB_NEEDRESOLVER #include #include #include #include #include #include #include #include "derparse.h" extern MECH_OID g_stcMechOIDList []; #define POWEROF2(x) (((x) & ((x)-1)) == 0) /* These two may be set by commands. */ int smb_debug, smb_verbose; /* * This used to call the DCE/RPC code. * We want more strict layering than this. * The redirector should simply export a * remote pipe API, comsumed by dce rpc. * Make it a no-op for now. */ #if 0 #include #else static void rpc_cleanup_smbctx(struct smb_ctx *ctx) { } #endif 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_ctx_ssn(struct smbioc_ossn *ssn) { printf(" srvname=\"%s\", dom=\"%s\", user=\"%s\", password=%s\n", ssn->ioc_srvname, ssn->ioc_workgroup, ssn->ioc_user, ssn->ioc_password[0] ? "(non-null)" : "NULL"); printf(" timeout=%d, retry=%d, owner=%d, group=%d\n", ssn->ioc_timeout, ssn->ioc_retrycount, ssn->ioc_owner, ssn->ioc_group); } void dump_ctx_sh(struct smbioc_oshare *sh) { printf(" share_name=\"%s\", share_pw=\"%s\"\n", sh->ioc_share, sh->ioc_password); } void dump_ctx(char *where, struct smb_ctx *ctx) { printf("context %s:\n", where); dump_ctx_flags(ctx->ct_flags); printf(" localname=\"%s\"", ctx->ct_locname); if (ctx->ct_fullserver) printf(" fullserver=\"%s\"", ctx->ct_fullserver); else printf(" fullserver=NULL"); if (ctx->ct_srvaddr) printf(" srvaddr=\"%s\"\n", ctx->ct_srvaddr); else printf(" srvaddr=NULL\n"); dump_ctx_ssn(&ctx->ct_ssn); dump_ctx_sh(&ctx->ct_sh); } /* * Initialize an smb_ctx struct. * * 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_init(struct smb_ctx *ctx, int argc, char *argv[], int minlevel, int maxlevel, int sharetype) { int opt, error = 0; const char *arg, *cp; struct passwd pw; char pwbuf[NSS_BUFLEN_PASSWD]; int aflg = 0, uflg = 0; bzero(ctx, sizeof (*ctx)); if (sharetype == SMB_ST_DISK) ctx->ct_flags |= SMBCF_BROWSEOK; error = nb_ctx_create(&ctx->ct_nb); if (error) return (error); ctx->ct_fd = -1; ctx->ct_parsedlevel = SMBL_NONE; ctx->ct_minlevel = minlevel; ctx->ct_maxlevel = maxlevel; ctx->ct_ssn.ioc_opt = SMBVOPT_CREATE | SMBVOPT_MINAUTH_NTLM; ctx->ct_ssn.ioc_timeout = 15; ctx->ct_ssn.ioc_retrycount = 4; ctx->ct_ssn.ioc_owner = SMBM_ANY_OWNER; ctx->ct_ssn.ioc_group = SMBM_ANY_GROUP; ctx->ct_ssn.ioc_mode = SMBM_EXEC; ctx->ct_ssn.ioc_rights = SMBM_DEFAULT; ctx->ct_sh.ioc_opt = SMBVOPT_CREATE; ctx->ct_sh.ioc_owner = SMBM_ANY_OWNER; ctx->ct_sh.ioc_group = SMBM_ANY_GROUP; ctx->ct_sh.ioc_mode = SMBM_EXEC; ctx->ct_sh.ioc_rights = SMBM_DEFAULT; ctx->ct_sh.ioc_owner = SMBM_ANY_OWNER; ctx->ct_sh.ioc_group = SMBM_ANY_GROUP; nb_ctx_setscope(ctx->ct_nb, ""); /* * if the user name is not specified some other way, * use the current user name (built-in default) */ if (getpwuid_r(geteuid(), &pw, pwbuf, sizeof (pwbuf)) != NULL) smb_ctx_setuser(ctx, pw.pw_name, 0); /* * Set a built-in default domain (workgroup). * XXX: What's the best default? Use "?" instead? * Using the Windows/NT default for now. */ smb_ctx_setworkgroup(ctx, "WORKGROUP", 0); /* * Parse the UNC path. Values from here are * marked as "from CMD". */ if (argv == NULL) goto done; for (opt = 1; opt < argc; opt++) { cp = argv[opt]; if (strncmp(cp, "//", 2) != 0) continue; error = smb_ctx_parseunc(ctx, cp, sharetype, &cp); if (error) return (error); break; } /* * Parse options, if any. Values from here too * are marked as "from CMD". */ while (error == 0 && (opt = cf_getopt(argc, argv, ":AU:E:L:")) != -1) { arg = cf_optarg; switch (opt) { case 'A': aflg = 1; error = smb_ctx_setuser(ctx, "", TRUE); error = smb_ctx_setpassword(ctx, "", TRUE); ctx->ct_flags |= SMBCF_NOPWD; break; case 'E': #if 0 /* We don't support any "charset" stuff. (ignore -E) */ error = smb_ctx_setcharset(ctx, arg); if (error) return (error); #endif break; case 'L': #if 0 /* Use the standard environment variables (ignore -L) */ error = nls_setlocale(optarg); if (error) break; #endif break; case 'U': uflg = 1; error = smb_ctx_setuser(ctx, arg, TRUE); break; } } if (aflg && uflg) { printf(gettext("-A and -U flags are exclusive.\n")); return (1); } cf_optind = cf_optreset = 1; done: if (smb_debug) dump_ctx("after smb_ctx_init", ctx); return (error); } void smb_ctx_done(struct smb_ctx *ctx) { rpc_cleanup_smbctx(ctx); /* Kerberos stuff. See smb_ctx_krb5init() */ if (ctx->ct_krb5ctx) { if (ctx->ct_krb5cp) krb5_free_principal(ctx->ct_krb5ctx, ctx->ct_krb5cp); krb5_free_context(ctx->ct_krb5ctx); } if (ctx->ct_fd != -1) close(ctx->ct_fd); #if 0 /* XXX: not pointers anymore */ if (&ctx->ct_ssn.ioc_server) nb_snbfree(&ctx->ct_ssn.ioc_server); if (&ctx->ct_ssn.ioc_local) nb_snbfree(&ctx->ct_ssn.ioc_local); #endif if (ctx->ct_srvaddr) free(ctx->ct_srvaddr); if (ctx->ct_nb) nb_ctx_done(ctx->ct_nb); if (ctx->ct_secblob) free(ctx->ct_secblob); if (ctx->ct_origshare) free(ctx->ct_origshare); if (ctx->ct_fullserver) free(ctx->ct_fullserver); } 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 sharetype, const char **next) { const char *p = unc; char *p1, *colon, *servername; char tmp[1024]; char tmp2[1024]; int error; ctx->ct_parsedlevel = SMBL_NONE; if (*p++ != '/' || *p++ != '/') { smb_error(dgettext(TEXT_DOMAIN, "UNC should start with '//'"), 0); return (EINVAL); } p1 = tmp; error = getsubstring(p, ';', p1, sizeof (tmp), &p); if (!error) { if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty workgroup name"), 0); return (EINVAL); } nls_str_upper(tmp, tmp); error = smb_ctx_setworkgroup(ctx, unpercent(tmp), TRUE); if (error) return (error); } 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); return (EINVAL); } p1 = strchr(tmp, ':'); if (p1) { colon += p1 - tmp; *p1++ = (char)0; error = smb_ctx_setpassword(ctx, unpercent(p1), TRUE); if (error) return (error); if (p - colon > 2) memset(colon+1, '*', p - colon - 2); } p1 = tmp; if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty user name"), 0); return (EINVAL); } error = smb_ctx_setuser(ctx, unpercent(tmp), TRUE); if (error) return (error); 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); return (error); } } if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty server name"), 0); return (EINVAL); } /* * It's safe to uppercase this string, which * consists of ascii characters that should * be uppercased, %s, and ascii characters representing * hex digits 0-9 and A-F (already uppercased, and * if not uppercased they need to be). However, * it is NOT safe to uppercase after it has been * converted, below! */ nls_str_upper(tmp2, tmp); /* * scan for % in the string. * If we find one, convert * to the assumed codepage. */ if (strchr(tmp2, '%')) { /* use the 1st buffer, we don't need the old string */ servername = tmp; if (!(servername = convert_utf8_to_wincs(unpercent(tmp2)))) { smb_error(dgettext(TEXT_DOMAIN, "bad server name"), 0); return (EINVAL); } /* * Converts utf8 to win equivalent of * what is configured on this machine. * Note that we are assuming this is the * encoding used on the server, and that * assumption might be incorrect. This is * the best we can do now, and we should * move to use port 445 to avoid having * to worry about server codepages. */ } else /* no conversion needed */ servername = tmp2; smb_ctx_setserver(ctx, servername); error = smb_ctx_setfullserver(ctx, servername); if (error) return (error); if (sharetype == SMB_ST_NONE) { *next = p; return (0); } if (*p != 0 && ctx->ct_maxlevel < SMBL_SHARE) { smb_error(dgettext(TEXT_DOMAIN, "no share name required"), 0); return (EINVAL); } 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); return (error); } } if (*p1 == 0 && ctx->ct_minlevel >= SMBL_SHARE && !(ctx->ct_flags & SMBCF_BROWSEOK)) { smb_error(dgettext(TEXT_DOMAIN, "empty share name"), 0); return (EINVAL); } *next = p; if (*p1 == 0) return (0); error = smb_ctx_setshare(ctx, unpercent(p1), sharetype); return (error); } 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); } int smb_ctx_setfullserver(struct smb_ctx *ctx, const char *name) { ctx->ct_fullserver = strdup(name); if (ctx->ct_fullserver == NULL) return (ENOMEM); return (0); } /* * XXX TODO FIXME etc etc * If the call to nbns_getnodestatus(...) fails we can try one of two other * methods; use a name of "*SMBSERVER", which is supported by Samba (at least) * or, as a last resort, try the "truncate-at-dot" heuristic. * And the heuristic really should attempt truncation at * each dot in turn, left to right. * * These fallback heuristics should be triggered when the attempt to open the * session fails instead of in the code below. * * See http://ietf.org/internet-drafts/draft-crhertel-smb-url-07.txt */ int smb_ctx_getnbname(struct smb_ctx *ctx, struct sockaddr *sap) { char server[SMB_MAXSRVNAMELEN + 1]; char workgroup[SMB_MAXUSERNAMELEN + 1]; int error; #if 0 char *dot; #endif server[0] = workgroup[0] = '\0'; error = nbns_getnodestatus(sap, ctx->ct_nb, server, workgroup); if (error == 0) { /* * Used to set our domain name to be the same as * the server's domain name. Unnecessary at best, * and wrong for accounts in a trusted domain. */ #ifdef APPLE if (workgroup[0] && !ctx->ct_ssn.ioc_workgroup[0]) smb_ctx_setworkgroup(ctx, workgroup, 0); #endif if (server[0]) smb_ctx_setserver(ctx, server); } else { if (smb_verbose) smb_error(dgettext(TEXT_DOMAIN, "Failed to get NetBIOS node status."), 0); if (ctx->ct_ssn.ioc_srvname[0] == (char)0) smb_ctx_setserver(ctx, "*SMBSERVER"); } #if 0 if (server[0] == (char)0) { dot = strchr(ctx->ct_fullserver, '.'); if (dot) *dot = '\0'; if (strlen(ctx->ct_fullserver) <= SMB_MAXSRVNAMELEN) { /* * don't uppercase the server name. it comes from * NBNS and uppercasing can clobber the characters */ strcpy(ctx->ct_ssn.ioc_srvname, ctx->ct_fullserver); error = 0; } else { error = -1; } if (dot) *dot = '.'; } #endif return (error); } /* this routine does not uppercase the server name */ void smb_ctx_setserver(struct smb_ctx *ctx, const char *name) { /* don't uppercase the server name */ if (strlen(name) > SMB_MAXSRVNAMELEN) { /* NB limit is 15 */ ctx->ct_ssn.ioc_srvname[0] = '\0'; } else strcpy(ctx->ct_ssn.ioc_srvname, name); } int smb_ctx_setuser(struct smb_ctx *ctx, const char *name, int from_cmd) { if (strlen(name) >= SMB_MAXUSERNAMELEN) { 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); /* don't uppercase the username, just copy it. */ strcpy(ctx->ct_ssn.ioc_user, name); /* Mark this as "from the command line". */ if (from_cmd) ctx->ct_flags |= SMBCF_CMD_USR; return (0); } /* * Never uppercase the workgroup * name here, because it might come * from a Windows codepage encoding. * * 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_setworkgroup(struct smb_ctx *ctx, const char *name, int from_cmd) { if (strlen(name) >= SMB_MAXUSERNAMELEN) { 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); strcpy(ctx->ct_ssn.ioc_workgroup, name); /* 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) { if (passwd == NULL) /* XXX Huh? */ return (EINVAL); if (strlen(passwd) >= SMB_MAXPASSWORDLEN) { smb_error(dgettext(TEXT_DOMAIN, "password too long"), 0); return (ENAMETOOLONG); } /* * Don't overwrite a value from the command line * with one from anywhere else. */ if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_PW)) return (0); if (strncmp(passwd, "$$1", 3) == 0) smb_simpledecrypt(ctx->ct_ssn.ioc_password, passwd); else strcpy(ctx->ct_ssn.ioc_password, passwd); strcpy(ctx->ct_sh.ioc_password, ctx->ct_ssn.ioc_password); /* Mark this as "from the command line". */ if (from_cmd) ctx->ct_flags |= SMBCF_CMD_PW; return (0); } int smb_ctx_setshare(struct smb_ctx *ctx, const char *share, int stype) { if (strlen(share) >= SMB_MAXSHARENAMELEN) { 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); nls_str_upper(ctx->ct_sh.ioc_share, share); if (share[0] != 0) ctx->ct_parsedlevel = SMBL_SHARE; ctx->ct_sh.ioc_stype = 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) free(ctx->ct_srvaddr); if ((ctx->ct_srvaddr = strdup(addr)) == NULL) return (ENOMEM); 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) { 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); } /* * 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': ctx->ct_ssn.ioc_rights = strtol(arg, &cp, 8); if (*cp == '/') { ctx->ct_sh.ioc_rights = strtol(cp + 1, &cp, 8); 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_sh.ioc_owner, &ctx->ct_sh.ioc_group); } if (*p && error == 0) { error = smb_parse_owner(cp, &ctx->ct_ssn.ioc_owner, &ctx->ct_ssn.ioc_group); } free(p); break; case 'P': /* ctx->ct_ssn.ioc_opt |= SMBCOPT_PERMANENT; */ break; case 'R': ctx->ct_ssn.ioc_retrycount = atoi(arg); break; case 'T': ctx->ct_ssn.ioc_timeout = atoi(arg); break; case 'W': nls_str_upper(tmp, arg); error = smb_ctx_setworkgroup(ctx, tmp, TRUE); break; } return (error); } #if 0 static void smb_hexdump(const uchar_t *buf, int len) { int ofs = 0; while (len--) { if (ofs % 16 == 0) printf("\n%02X: ", ofs); printf("%02x ", *buf++); ofs++; } printf("\n"); } #endif static int smb_addiconvtbl(const char *to, const char *from, const uchar_t *tbl) { int error; /* * Not able to find out what is the work of this routine till * now. Still investigating. * REVISIT */ #ifdef KICONV_SUPPORT 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); } #endif return (0); } /* * Verify context before connect operation(s), * lookup specified server and try to fill all forgotten fields. */ int smb_ctx_resolve(struct smb_ctx *ctx) { struct smbioc_ossn *ssn = &ctx->ct_ssn; struct smbioc_oshare *sh = &ctx->ct_sh; struct nb_name nn; struct sockaddr *sap; struct sockaddr_nb *salocal, *saserver; char *cp; uchar_t cstbl[256]; uint_t i; int error = 0; int browseok = ctx->ct_flags & SMBCF_BROWSEOK; int renego = 0; ctx->ct_flags &= ~SMBCF_RESOLVED; if (isatty(STDIN_FILENO)) browseok = 0; if (ctx->ct_fullserver == NULL || ctx->ct_fullserver[0] == 0) { smb_error(dgettext(TEXT_DOMAIN, "no server name specified"), 0); return (EINVAL); } if (ctx->ct_minlevel >= SMBL_SHARE && sh->ioc_share[0] == 0 && !browseok) { smb_error(dgettext(TEXT_DOMAIN, "no share name specified for %s@%s"), 0, ssn->ioc_user, ssn->ioc_srvname); return (EINVAL); } error = nb_ctx_resolve(ctx->ct_nb); if (error) return (error); 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); } /* * If we have an explicit address set for the server in * an "addr=X" setting in .nsmbrc or SMF, just try using a * gethostbyname() lookup for it. */ if (ctx->ct_srvaddr) { error = nb_resolvehost_in(ctx->ct_srvaddr, &sap); if (error == 0) (void) smb_ctx_getnbname(ctx, sap); } else error = -1; /* * Next try a gethostbyname() lookup on the original user- * specified server name. This is similar to Windows * NBT option "Use DNS for name resolution." */ if (error && ctx->ct_fullserver) { error = nb_resolvehost_in(ctx->ct_fullserver, &sap); if (error == 0) (void) smb_ctx_getnbname(ctx, sap); } /* * Finally, try the shorter, upper-cased ssn->ioc_srvname * with a NBNS/WINS lookup if the "nbns_enable" property is * true (the default). nbns_resolvename() may unicast to the * "nbns" server or broadcast on the subnet. */ if (error && ssn->ioc_srvname[0] && ctx->ct_nb->nb_flags & NBCF_NS_ENABLE) { error = nbns_resolvename(ssn->ioc_srvname, ctx->ct_nb, &sap); /* * Used to get the NetBIOS node status here. * Not necessary (we have the NetBIOS name). */ } if (error) { smb_error(dgettext(TEXT_DOMAIN, "can't get server address"), error); return (error); } /* XXX: no nls_str_upper(ssn->ioc_srvname) here? */ assert(sizeof (nn.nn_name) == sizeof (ssn->ioc_srvname)); memcpy(nn.nn_name, ssn->ioc_srvname, NB_NAMELEN); nn.nn_type = NBT_SERVER; nn.nn_scope = ctx->ct_nb->nb_scope; error = nb_sockaddr(sap, &nn, &saserver); memcpy(&ctx->ct_srvinaddr, sap, sizeof (struct sockaddr_in)); nb_snbfree(sap); if (error) { smb_error(dgettext(TEXT_DOMAIN, "can't allocate server address"), error); return (error); } /* We know it's a NetBIOS address here. */ bcopy(saserver, &ssn->ioc_server.nb, sizeof (struct sockaddr_nb)); if (ctx->ct_locname[0] == 0) { error = nb_getlocalname(ctx->ct_locname, SMB_MAXUSERNAMELEN + 1); if (error) { smb_error(dgettext(TEXT_DOMAIN, "can't get local name"), error); return (error); } nls_str_upper(ctx->ct_locname, ctx->ct_locname); } /* XXX: no nls_str_upper(ctx->ct_locname); here? */ memcpy(nn.nn_name, ctx->ct_locname, NB_NAMELEN); nn.nn_type = NBT_WKSTA; nn.nn_scope = ctx->ct_nb->nb_scope; error = nb_sockaddr(NULL, &nn, &salocal); if (error) { nb_snbfree((struct sockaddr *)saserver); smb_error(dgettext(TEXT_DOMAIN, "can't allocate local address"), error); return (error); } /* We know it's a NetBIOS address here. */ bcopy(salocal, &ssn->ioc_local.nb, sizeof (struct sockaddr_nb)); error = smb_ctx_negotiate(ctx, SMBL_SHARE, SMBLK_CREATE, ssn->ioc_workgroup); if (error) return (error); ctx->ct_flags &= ~SMBCF_AUTHREQ; if (!ctx->ct_secblob && browseok && !sh->ioc_share[0] && !(ctx->ct_flags & SMBCF_XXX)) { /* assert: anon share list is subset of overall server shares */ error = smb_browse(ctx, 1); if (error) /* user cancel or other error? */ return (error); /* * A share was selected, authenticate button was pressed, * or anon-authentication failed getting browse list. */ } if ((ctx->ct_secblob == NULL) && (ctx->ct_flags & SMBCF_AUTHREQ || (ssn->ioc_password[0] == '\0' && !(ctx->ct_flags & SMBCF_NOPWD)))) { reauth: /* * This function is implemented in both * ui-apple.c and ui-sun.c so let's try to * keep the same interface. Not sure why * they didn't just pass ssn here. */ error = smb_get_authentication( ssn->ioc_workgroup, sizeof (ssn->ioc_workgroup) - 1, ssn->ioc_user, sizeof (ssn->ioc_user) - 1, ssn->ioc_password, sizeof (ssn->ioc_password) - 1, ssn->ioc_srvname, ctx); if (error) return (error); } /* * if we have a session it is either anonymous * or from a stale authentication. re-negotiating * gets us ready for a fresh session */ if (ctx->ct_flags & SMBCF_SSNACTIVE || renego) { renego = 0; /* don't clobber workgroup name, pass null arg */ error = smb_ctx_negotiate(ctx, SMBL_SHARE, SMBLK_CREATE, NULL); if (error) return (error); } if (browseok && !sh->ioc_share[0]) { ctx->ct_flags &= ~SMBCF_AUTHREQ; error = smb_browse(ctx, 0); if (ctx->ct_flags & SMBCF_KCFOUND && smb_autherr(error)) { smb_error(dgettext(TEXT_DOMAIN, "smb_ctx_resolve: bad keychain entry"), 0); ctx->ct_flags |= SMBCF_KCBAD; renego = 1; goto reauth; } if (error) /* auth, user cancel, or other error */ return (error); /* * Re-authenticate button was pressed? */ if (ctx->ct_flags & SMBCF_AUTHREQ) goto reauth; if (!sh->ioc_share[0] && !(ctx->ct_flags & SMBCF_XXX)) { smb_error(dgettext(TEXT_DOMAIN, "no share specified for %s@%s"), 0, ssn->ioc_user, ssn->ioc_srvname); return (EINVAL); } } ctx->ct_flags |= SMBCF_RESOLVED; if (smb_debug) dump_ctx("after smb_ctx_resolve", ctx); return (0); } int smb_open_driver() { char buf[20]; int err, fd, i; uint32_t version; /* * First try to open as clone */ fd = open("/dev/"NSMB_NAME, O_RDWR); if (fd >= 0) goto opened; err = errno; /* from open */ #ifdef APPLE /* * well, no clone capabilities available - we have to scan * all devices in order to get free one */ for (i = 0; i < 1024; i++) { snprintf(buf, sizeof (buf), "/dev/%s%d", NSMB_NAME, i); fd = open(buf, O_RDWR); if (fd >= 0) goto opened; if (i && POWEROF2(i+1)) smb_error(dgettext(TEXT_DOMAIN, "%d failures to open smb device"), errno, i+1); } err = ENOENT; #endif smb_error(dgettext(TEXT_DOMAIN, "failed to open %s"), err, "/dev/" NSMB_NAME); return (-1); opened: /* * Check the driver version (paranoia) * Do this BEFORE any other ioctl calls. */ if (ioctl(fd, SMBIOC_GETVERS, &version) < 0) { err = errno; smb_error(dgettext(TEXT_DOMAIN, "failed to get driver version"), err); close(fd); return (-1); } if (version != NSMB_VERSION) { smb_error(dgettext(TEXT_DOMAIN, "incorrect driver version"), 0); close(fd); return (-1); } return (fd); } static int smb_ctx_gethandle(struct smb_ctx *ctx) { int err, fd; if (ctx->ct_fd != -1) { rpc_cleanup_smbctx(ctx); close(ctx->ct_fd); ctx->ct_fd = -1; ctx->ct_flags &= ~SMBCF_SSNACTIVE; } fd = smb_open_driver(); if (fd < 0) return (ENODEV); ctx->ct_fd = fd; return (0); } int smb_ctx_ioctl(struct smb_ctx *ctx, int inum, struct smbioc_lookup *rqp) { size_t siz = DEF_SEC_TOKEN_LEN; int rc = 0; struct sockaddr sap1, sap2; int i; if (rqp->ioc_ssn.ioc_outtok) free(rqp->ioc_ssn.ioc_outtok); rqp->ioc_ssn.ioc_outtoklen = siz; rqp->ioc_ssn.ioc_outtok = malloc(siz+1); if (rqp->ioc_ssn.ioc_outtok == NULL) return (ENOMEM); bzero(rqp->ioc_ssn.ioc_outtok, siz+1); /* Note: No longer put length in outtok[0] */ /* *((int *)rqp->ioc_ssn.ioc_outtok) = (int)siz; */ seteuid(eff_uid); /* restore setuid root briefly */ if (ioctl(ctx->ct_fd, inum, rqp) == -1) { rc = errno; goto out; } if (rqp->ioc_ssn.ioc_outtoklen <= siz) goto out; /* * Operation completed, but our output token wasn't large enough. * The re-call below only pulls the token from the kernel. */ siz = rqp->ioc_ssn.ioc_outtoklen; free(rqp->ioc_ssn.ioc_outtok); rqp->ioc_ssn.ioc_outtok = malloc(siz + 1); if (rqp->ioc_ssn.ioc_outtok == NULL) { rc = ENOMEM; goto out; } bzero(rqp->ioc_ssn.ioc_outtok, siz+1); /* Note: No longer put length in outtok[0] */ /* *((int *)rqp->ioc_ssn.ioc_outtok) = siz; */ if (ioctl(ctx->ct_fd, inum, rqp) == -1) rc = errno; out: seteuid(real_uid); /* and back to real user */ return (rc); } /* * adds a GSSAPI wrapper */ char * smb_ctx_tkt2gtok(uchar_t *tkt, ulong_t tktlen, uchar_t **gtokp, ulong_t *gtoklenp) { ulong_t bloblen = tktlen; ulong_t len; uchar_t krbapreq[2] = "\x01\x00"; /* see RFC 1964 */ char *failure; uchar_t *blob = NULL; /* result */ uchar_t *b; bloblen += sizeof (krbapreq); bloblen += g_stcMechOIDList[spnego_mech_oid_Kerberos_V5].iLen; len = bloblen; bloblen = ASNDerCalcTokenLength(bloblen, bloblen); failure = dgettext(TEXT_DOMAIN, "smb_ctx_tkt2gtok malloc"); if (!(blob = malloc(bloblen))) goto out; b = blob; b += ASNDerWriteToken(b, SPNEGO_NEGINIT_APP_CONSTRUCT, NULL, len); b += ASNDerWriteOID(b, spnego_mech_oid_Kerberos_V5); memcpy(b, krbapreq, sizeof (krbapreq)); b += sizeof (krbapreq); failure = dgettext(TEXT_DOMAIN, "smb_ctx_tkt2gtok insanity check"); if (b + tktlen != blob + bloblen) goto out; memcpy(b, tkt, tktlen); *gtoklenp = bloblen; *gtokp = blob; failure = NULL; out:; if (blob && failure) free(blob); return (failure); } /* * Initialization for Kerberos, pulled out of smb_ctx_principal2tkt. * This just gets our cached credentials, if we have any. * Based on the "klist" command. */ char * smb_ctx_krb5init(struct smb_ctx *ctx) { char *failure; krb5_error_code kerr; krb5_context kctx = NULL; krb5_ccache kcc = NULL; krb5_principal kprin = NULL; kerr = krb5_init_context(&kctx); if (kerr) { failure = "krb5_init_context"; goto out; } ctx->ct_krb5ctx = kctx; /* non-default would instead use krb5_cc_resolve */ kerr = krb5_cc_default(kctx, &kcc); if (kerr) { failure = "krb5_cc_default"; goto out; } ctx->ct_krb5cc = kcc; /* * Get the client principal (ticket), * or find out if we don't have one. */ kerr = krb5_cc_get_principal(kctx, kcc, &kprin); if (kerr) { failure = "krb5_cc_get_principal"; goto out; } ctx->ct_krb5cp = kprin; if (smb_verbose) { fprintf(stderr, gettext("Ticket cache: %s:%s\n"), krb5_cc_get_type(kctx, kcc), krb5_cc_get_name(kctx, kcc)); } failure = NULL; out: return (failure); } /* * See "Windows 2000 Kerberos Interoperability" paper by * Christopher Nebergall. RC4 HMAC is the W2K default but * Samba support lagged (not due to Samba itself, but due to OS' * Kerberos implementations.) * * Only session enc type should matter, not ticket enc type, * per Sam Hartman on krbdev. * * Preauthentication failure topics in krb-protocol may help here... * try "John Brezak" and/or "Clifford Neuman" too. */ static krb5_enctype kenctypes[] = { ENCTYPE_ARCFOUR_HMAC, /* defined in Tiger krb5.h */ ENCTYPE_DES_CBC_MD5, ENCTYPE_DES_CBC_CRC, ENCTYPE_NULL }; /* * Obtain a kerberos ticket... * (if TLD != "gov" then pray first) */ char * smb_ctx_principal2tkt( struct smb_ctx *ctx, char *prin, uchar_t **tktp, ulong_t *tktlenp) { char *failure; krb5_context kctx = NULL; krb5_error_code kerr; krb5_ccache kcc = NULL; krb5_principal kprin = NULL, cprn = NULL; krb5_creds kcreds, *kcredsp = NULL; krb5_auth_context kauth = NULL; krb5_data kdata, kdata0; uchar_t *tkt; memset((char *)&kcreds, 0, sizeof (kcreds)); kdata0.length = 0; /* These shoud have been done in smb_ctx_krb5init() */ if (ctx->ct_krb5ctx == NULL || ctx->ct_krb5cc == NULL || ctx->ct_krb5cp == NULL) { failure = "smb_ctx_krb5init"; goto out; } kctx = ctx->ct_krb5ctx; kcc = ctx->ct_krb5cc; cprn = ctx->ct_krb5cp; failure = "krb5_set_default_tgs_enctypes"; if ((kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes))) goto out; /* * The following is an unrolling of krb5_mk_req. Something like: * krb5_mk_req(kctx, &kauth, 0, service(prin), hostname(prin), * &kdata0, kcc, &kdata);) * ...except we needed krb5_parse_name not krb5_sname_to_principal. */ failure = "krb5_parse_name"; if ((kerr = krb5_parse_name(kctx, prin, &kprin))) goto out; failure = "krb5_copy_principal(server)"; if ((kerr = krb5_copy_principal(kctx, kprin, &kcreds.server))) goto out; failure = "krb5_copy_principal(client)"; if ((kerr = krb5_copy_principal(kctx, cprn, &kcreds.client))) goto out; failure = "krb5_get_credentials"; if ((kerr = krb5_get_credentials(kctx, 0, kcc, &kcreds, &kcredsp))) goto out; failure = "krb5_mk_req_extended"; if ((kerr = krb5_mk_req_extended(kctx, &kauth, 0, &kdata0, kcredsp, &kdata))) goto out; failure = "malloc"; if (!(tkt = malloc(kdata.length))) { krb5_free_data_contents(kctx, &kdata); goto out; } *tktlenp = kdata.length; memcpy(tkt, kdata.data, kdata.length); krb5_free_data_contents(kctx, &kdata); *tktp = tkt; failure = NULL; out:; if (kerr) { if (!failure) failure = "smb_ctx_principal2tkt"; /* * Avoid logging the typical "No credentials cache found" */ if (kerr != KRB5_FCC_NOFILE || strcmp(failure, "krb5_cc_get_principal")) com_err(__progname, kerr, failure); } if (kauth) krb5_auth_con_free(kctx, kauth); if (kcredsp) krb5_free_creds(kctx, kcredsp); if (kcreds.server || kcreds.client) krb5_free_cred_contents(kctx, &kcreds); if (kprin) krb5_free_principal(kctx, kprin); /* Free kctx in smb_ctx_done */ return (failure); } char * smb_ctx_principal2blob( struct smb_ctx *ctx, smbioc_ossn_t *ssn, char *prin) { int rc = 0; char *failure; uchar_t *tkt = NULL; ulong_t tktlen; uchar_t *gtok = NULL; /* gssapi token */ ulong_t gtoklen; /* gssapi token length */ SPNEGO_TOKEN_HANDLE stok = NULL; /* spnego token */ void *blob = NULL; /* result */ ulong_t bloblen; /* result length */ if ((failure = smb_ctx_principal2tkt(ctx, prin, &tkt, &tktlen))) goto out; if ((failure = smb_ctx_tkt2gtok(tkt, tktlen, >ok, >oklen))) goto out; /* * RFC says to send NegTokenTarg now. So does MS docs. But * win2k gives ERRbaduid if we do... we must send * another NegTokenInit now! */ failure = "spnegoCreateNegTokenInit"; if ((rc = spnegoCreateNegTokenInit(spnego_mech_oid_Kerberos_V5_Legacy, 0, gtok, gtoklen, NULL, 0, &stok))) goto out; failure = "spnegoTokenGetBinary(NULL)"; rc = spnegoTokenGetBinary(stok, NULL, &bloblen); if (rc != SPNEGO_E_BUFFER_TOO_SMALL) goto out; failure = "malloc"; if (!(blob = malloc((size_t)bloblen))) goto out; /* No longer store length at start of blob. */ /* *blob = bloblen; */ failure = "spnegoTokenGetBinary"; if ((rc = spnegoTokenGetBinary(stok, blob, &bloblen))) goto out; ssn->ioc_intoklen = bloblen; ssn->ioc_intok = blob; failure = NULL; out:; if (rc) { /* XXX better is to embed rc in failure */ smb_error(dgettext(TEXT_DOMAIN, "spnego principal2blob error %d"), 0, -rc); if (!failure) failure = "spnego"; } if (blob && failure) free(blob); if (stok) spnegoFreeData(stok); if (gtok) free(gtok); if (tkt) free(tkt); return (failure); } #if 0 void prblob(uchar_t *b, size_t len) { while (len--) fprintf(stderr, "%02x", *b++); fprintf(stderr, "\n"); } #endif /* * We navigate the SPNEGO & ASN1 encoding to find a kerberos principal * Note: driver no longer puts length at start of blob. */ char * smb_ctx_blob2principal( struct smb_ctx *ctx, smbioc_ossn_t *ssn, char **prinp) { uchar_t *blob = ssn->ioc_outtok; size_t len = ssn->ioc_outtoklen; int rc = 0; SPNEGO_TOKEN_HANDLE stok = NULL; int indx = 0; char *failure; uchar_t flags = 0; unsigned long plen = 0; uchar_t *prin; #if 0 fprintf(stderr, "blob from negotiate:\n"); prblob(blob, len); #endif /* Skip the GUID */ assert(len >= SMB_GUIDLEN); blob += SMB_GUIDLEN; len -= SMB_GUIDLEN; failure = "spnegoInitFromBinary"; if ((rc = spnegoInitFromBinary(blob, len, &stok))) goto out; /* * Needn't use new Kerberos OID - the Legacy one is fine. */ failure = "spnegoIsMechTypeAvailable"; if (spnegoIsMechTypeAvailable(stok, spnego_mech_oid_Kerberos_V5_Legacy, &indx)) goto out; /* * Ignoring optional context flags for now. May want to pass * them to krb5 layer. XXX */ if (!spnegoGetContextFlags(stok, &flags)) fprintf(stderr, dgettext(TEXT_DOMAIN, "spnego context flags 0x%x\n"), flags); failure = "spnegoGetMechListMIC(NULL)"; rc = spnegoGetMechListMIC(stok, NULL, &plen); if (rc != SPNEGO_E_BUFFER_TOO_SMALL) goto out; failure = "malloc"; if (!(prin = malloc(plen + 1))) goto out; failure = "spnegoGetMechListMIC"; if ((rc = spnegoGetMechListMIC(stok, prin, &plen))) { free(prin); goto out; } prin[plen] = '\0'; *prinp = (char *)prin; failure = NULL; out:; if (stok) spnegoFreeData(stok); if (rc) { /* XXX better is to embed rc in failure */ smb_error(dgettext(TEXT_DOMAIN, "spnego blob2principal error %d"), 0, -rc); if (!failure) failure = "spnego"; } return (failure); } int smb_ctx_negotiate(struct smb_ctx *ctx, int level, int flags, char *workgroup) { struct smbioc_lookup rq; int error = 0; char *failure = NULL; char *principal = NULL; char c; int i; ssize_t *outtoklen; uchar_t *blob; /* * We leave ct_secblob set iff extended security * negotiation succeeds. */ if (ctx->ct_secblob) { free(ctx->ct_secblob); ctx->ct_secblob = NULL; } #ifdef XXX if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) { smb_error(dgettext(TEXT_DOMAIN, "smb_ctx_lookup() data is not resolved"), 0); return (EINVAL); } #endif if ((error = smb_ctx_gethandle(ctx))) return (error); bzero(&rq, sizeof (rq)); bcopy(&ctx->ct_ssn, &rq.ioc_ssn, sizeof (struct smbioc_ossn)); bcopy(&ctx->ct_sh, &rq.ioc_sh, sizeof (struct smbioc_oshare)); /* * Find out if we have a Kerberos ticket, * and only offer SPNEGO if we have one. */ failure = smb_ctx_krb5init(ctx); if (failure) { if (smb_verbose) smb_error(failure, 0); goto out; } rq.ioc_flags = flags; rq.ioc_level = level; rq.ioc_ssn.ioc_opt |= SMBVOPT_EXT_SEC; error = smb_ctx_ioctl(ctx, SMBIOC_NEGOTIATE, &rq); if (error) { failure = dgettext(TEXT_DOMAIN, "negotiate failed"); smb_error(failure, error); if (error == ETIMEDOUT) return (error); goto out; } /* * If the server capabilities did not include * SMB_CAP_EXT_SECURITY then the driver clears * the flag SMBVOPT_EXT_SEC for us. * XXX: should add the capabilities to ioc_ssn * XXX: see comment in driver - smb_usr.c */ failure = dgettext(TEXT_DOMAIN, "SPNEGO unsupported"); if ((rq.ioc_ssn.ioc_opt & SMBVOPT_EXT_SEC) == 0) { if (smb_verbose) smb_error(failure, 0); /* * Do regular (old style) NTLM or NTLMv2 * Nothing more to do here in negotiate. */ return (0); } /* * Capabilities DO include SMB_CAP_EXT_SECURITY, * so this should be an SPNEGO security blob. * Parse the ASN.1/DER, prepare response(s). * XXX: Handle STATUS_MORE_PROCESSING_REQUIRED? * XXX: Requires additional session setup calls. */ if (rq.ioc_ssn.ioc_outtoklen <= SMB_GUIDLEN) goto out; /* some servers send padding junk */ blob = rq.ioc_ssn.ioc_outtok; if (blob[0] == 0) goto out; failure = smb_ctx_blob2principal( ctx, &rq.ioc_ssn, &principal); if (failure) goto out; failure = smb_ctx_principal2blob( ctx, &rq.ioc_ssn, principal); if (failure) goto out; /* Success! Save the blob to send next. */ ctx->ct_secblob = rq.ioc_ssn.ioc_intok; ctx->ct_secbloblen = rq.ioc_ssn.ioc_intoklen; rq.ioc_ssn.ioc_intok = NULL; out: if (principal) free(principal); if (rq.ioc_ssn.ioc_intok) free(rq.ioc_ssn.ioc_intok); if (rq.ioc_ssn.ioc_outtok) free(rq.ioc_ssn.ioc_outtok); if (!failure) return (0); /* Success! */ /* * Negotiate failed with "extended security". * * XXX: If we are doing SPNEGO correctly, * we should never get here unless the user * supplied invalid authentication data, * or we saw some kind of protocol error. * * XXX: The error message below should be * XXX: unconditional (remove "if verbose") * XXX: but not until we have "NTLMSSP" * Avoid spew for anticipated failure modes * but enable this with the verbose flag */ if (smb_verbose) { smb_error(dgettext(TEXT_DOMAIN, "%s (extended security negotiate)"), error, failure); } /* * XXX: Try again using NTLM (or NTLMv2) * XXX: Normal clients don't do this. * XXX: Should just return an error, but * keep the fall-back to NTLM for now. * * Start over with a new connection. */ if ((error = smb_ctx_gethandle(ctx))) return (error); bzero(&rq, sizeof (rq)); bcopy(&ctx->ct_ssn, &rq.ioc_ssn, sizeof (struct smbioc_ossn)); bcopy(&ctx->ct_sh, &rq.ioc_sh, sizeof (struct smbioc_oshare)); rq.ioc_flags = flags; rq.ioc_level = level; /* Note: NO SMBVOPT_EXT_SEC */ error = smb_ctx_ioctl(ctx, SMBIOC_NEGOTIATE, &rq); if (error) { failure = dgettext(TEXT_DOMAIN, "negotiate failed"); smb_error(failure, error); rpc_cleanup_smbctx(ctx); close(ctx->ct_fd); ctx->ct_fd = -1; return (error); } /* * Used to copy the workgroup out of the SMB_NEGOTIATE response * here, to default our domain name to be the same as the server. * Not a good idea: Unnecessary at best, and sometimes wrong, i.e. * when our account is in a trusted domain. */ return (error); } int smb_ctx_tdis(struct smb_ctx *ctx) { struct smbioc_lookup rq; /* XXX may be used, someday */ int error = 0; if (ctx->ct_fd < 0) { smb_error(dgettext(TEXT_DOMAIN, "tree disconnect without handle?!"), 0); return (EINVAL); } if (!(ctx->ct_flags & SMBCF_SSNACTIVE)) { smb_error(dgettext(TEXT_DOMAIN, "tree disconnect without session?!"), 0); return (EINVAL); } bzero(&rq, sizeof (rq)); bcopy(&ctx->ct_ssn, &rq.ioc_ssn, sizeof (struct smbioc_ossn)); bcopy(&ctx->ct_sh, &rq.ioc_sh, sizeof (struct smbioc_oshare)); if (ioctl(ctx->ct_fd, SMBIOC_TDIS, &rq) == -1) { error = errno; smb_error(dgettext(TEXT_DOMAIN, "tree disconnect failed"), error); } return (error); } int smb_ctx_lookup(struct smb_ctx *ctx, int level, int flags) { struct smbioc_lookup rq; int error = 0; char *failure = NULL; if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) { smb_error(dgettext(TEXT_DOMAIN, "smb_ctx_lookup() data is not resolved"), 0); return (EINVAL); } if (ctx->ct_fd < 0) { smb_error(dgettext(TEXT_DOMAIN, "handle from smb_ctx_nego() gone?!"), 0); return (EINVAL); } if (!(flags & SMBLK_CREATE)) return (0); bzero(&rq, sizeof (rq)); bcopy(&ctx->ct_ssn, &rq.ioc_ssn, sizeof (struct smbioc_ossn)); bcopy(&ctx->ct_sh, &rq.ioc_sh, sizeof (struct smbioc_oshare)); rq.ioc_flags = flags; rq.ioc_level = level; /* * Iff we have a security blob, we're using * extended security... */ if (ctx->ct_secblob) { rq.ioc_ssn.ioc_opt |= SMBVOPT_EXT_SEC; if (!(ctx->ct_flags & SMBCF_SSNACTIVE)) { rq.ioc_ssn.ioc_intok = ctx->ct_secblob; rq.ioc_ssn.ioc_intoklen = ctx->ct_secbloblen; error = smb_ctx_ioctl(ctx, SMBIOC_SSNSETUP, &rq); } rq.ioc_ssn.ioc_intok = NULL; if (error) { failure = dgettext(TEXT_DOMAIN, "session setup failed"); } else { ctx->ct_flags |= SMBCF_SSNACTIVE; if ((error = smb_ctx_ioctl(ctx, SMBIOC_TCON, &rq))) failure = dgettext(TEXT_DOMAIN, "tree connect failed"); } if (rq.ioc_ssn.ioc_intok) free(rq.ioc_ssn.ioc_intok); if (rq.ioc_ssn.ioc_outtok) free(rq.ioc_ssn.ioc_outtok); if (!failure) return (0); smb_error(dgettext(TEXT_DOMAIN, "%s (extended security lookup2)"), error, failure); /* unwise to failback to NTLM now */ return (error); } /* * Otherwise we're doing plain old NTLM */ seteuid(eff_uid); /* restore setuid root briefly */ if ((ctx->ct_flags & SMBCF_SSNACTIVE) == 0) { /* * This is the magic that tells the driver to * copy the password from the keychain, and * whether to use the system name or the * account domain to lookup the keychain. */ if (ctx->ct_flags & SMBCF_KCFOUND) rq.ioc_ssn.ioc_opt |= SMBVOPT_USE_KEYCHAIN; if (ctx->ct_flags & SMBCF_KCDOMAIN) rq.ioc_ssn.ioc_opt |= SMBVOPT_KC_DOMAIN; if (ioctl(ctx->ct_fd, SMBIOC_SSNSETUP, &rq) < 0) { error = errno; failure = dgettext(TEXT_DOMAIN, "session setup"); goto out; } ctx->ct_flags |= SMBCF_SSNACTIVE; } if (ioctl(ctx->ct_fd, SMBIOC_TCON, &rq) == -1) { error = errno; failure = dgettext(TEXT_DOMAIN, "tree connect"); } out: seteuid(real_uid); /* and back to real user */ if (failure) { error = errno; smb_error(dgettext(TEXT_DOMAIN, "%s phase failed"), error, failure); } return (error); } /* * Return the hflags2 word for an smb_ctx. */ int smb_ctx_flags2(struct smb_ctx *ctx) { uint16_t flags2; if (ioctl(ctx->ct_fd, SMBIOC_FLAGS2, &flags2) == -1) { smb_error(dgettext(TEXT_DOMAIN, "can't get flags2 for a session"), errno); return (-1); } printf(dgettext(TEXT_DOMAIN, "Flags2 value is %d\n"), flags2); return (flags2); } /* * 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 NOT_DEFINED 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_getint(smb_rc, sname, "timeout", &ctx->ct_ssn.ioc_timeout); #ifdef NOT_DEFINED rc_getint(smb_rc, sname, "retry_count", &ctx->ct_ssn.ioc_retrycount); rc_getstringptr(smb_rc, sname, "use_negprot_domain", &p); if (p && strcmp(p, "NO") == 0) ctx->ct_flags |= SMBCF_NONEGDOM; #endif rc_getstringptr(smb_rc, sname, "minauth", &p); if (p) { /* * "minauth" was set in this section; override * the current minimum authentication setting. */ ctx->ct_ssn.ioc_opt &= ~SMBVOPT_MINAUTH; if (strcmp(p, "kerberos") == 0) { /* * Don't fall back to NTLMv2, NTLMv1, or * a clear text password. */ ctx->ct_ssn.ioc_opt |= SMBVOPT_MINAUTH_KERBEROS; } else if (strcmp(p, "ntlmv2") == 0) { /* * Don't fall back to NTLMv1 or a clear * text password. */ ctx->ct_ssn.ioc_opt |= SMBVOPT_MINAUTH_NTLMV2; } else if (strcmp(p, "ntlm") == 0) { /* * Don't send the LM response over the wire. */ ctx->ct_ssn.ioc_opt |= SMBVOPT_MINAUTH_NTLM; } else if (strcmp(p, "lm") == 0) { /* * Fail if the server doesn't do encrypted * passwords. */ ctx->ct_ssn.ioc_opt |= SMBVOPT_MINAUTH_LM; } else if (strcmp(p, "none") == 0) { /* * Anything goes. * (The following statement should be * optimized away.) */ /* LINTED */ ctx->ct_ssn.ioc_opt |= SMBVOPT_MINAUTH_NONE; } 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); } } /* * 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) { nls_str_upper(p, p); error = smb_ctx_setworkgroup(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) { nls_str_upper(p, p); error = smb_ctx_setworkgroup(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 sname[SMB_MAXSRVNAMELEN + SMB_MAXUSERNAMELEN + SMB_MAXSHARENAMELEN + 4]; if (smb_open_rcfile(ctx) != 0) 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_ssn.ioc_srvname[0] == 0) goto done; /* * SERVER parameters. */ smb_ctx_readrcsection(ctx, ctx->ct_ssn.ioc_srvname, 1); /* * If we don't have a user name, we can't read any of the * [server:user...] sections. */ if (ctx->ct_ssn.ioc_user[0] == 0) goto done; /* * SERVER:USER parameters */ snprintf(sname, sizeof (sname), "%s:%s", ctx->ct_ssn.ioc_srvname, ctx->ct_ssn.ioc_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_sh.ioc_share[0] != 0) { /* * SERVER:USER:SHARE parameters */ snprintf(sname, sizeof (sname), "%s:%s:%s", ctx->ct_ssn.ioc_srvname, ctx->ct_ssn.ioc_user, ctx->ct_sh.ioc_share); smb_ctx_readrcsection(ctx, sname, 3); } done: if (smb_debug) dump_ctx("after smb_ctx_readrc", ctx); return (0); }