diff options
author | thurlow <none@none> | 2008-02-13 19:51:22 -0800 |
---|---|---|
committer | thurlow <none@none> | 2008-02-13 19:51:22 -0800 |
commit | 4bff34e37def8a90f9194d81bc345c52ba20086a (patch) | |
tree | 7bf2710d9da099e3b07fea38e12788bfd565f3c5 /usr/src/lib/libsmbfs/smb/ctx.c | |
parent | a916d99c7b27a531bf37c57f83b0b74120fd05bb (diff) | |
download | illumos-joyent-4bff34e37def8a90f9194d81bc345c52ba20086a.tar.gz |
PSARC 2005/695 CIFS Client on Solaris
PSARC 2007/303 pam_smb_login
PSARC 2008/073 CIFS Client on Solaris - Updates
6651904 CIFS Client - PSARC 2005/695
Diffstat (limited to 'usr/src/lib/libsmbfs/smb/ctx.c')
-rw-r--r-- | usr/src/lib/libsmbfs/smb/ctx.c | 2140 |
1 files changed, 2140 insertions, 0 deletions
diff --git a/usr/src/lib/libsmbfs/smb/ctx.c b/usr/src/lib/libsmbfs/smb/ctx.c new file mode 100644 index 0000000000..51896bf128 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ctx.c @@ -0,0 +1,2140 @@ +/* + * 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 <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 <kerberosv5/krb5.h> +#include <kerberosv5/com_err.h> + +extern uid_t real_uid, eff_uid; + +#define NB_NEEDRESOLVER + +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> +#include <cflib.h> +#include <charsets.h> + +#include <spnego.h> +#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 <rpc_cleanup.h> +#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); +} |