/* * Copyright (c) 2000-2002, 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.24 2002/04/13 14:35:28 bp Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NB_NEEDRESOLVER #include #include #include #include #include /* * Prescan command line for [-U user] argument * and fill context with defaults */ int smb_ctx_init(struct smb_ctx *ctx, int argc, char *argv[], int minlevel, int maxlevel, int sharetype) { int opt, error = 0; uid_t euid; const char *arg, *cp; struct passwd *pwd; bzero(ctx,sizeof(*ctx)); 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; 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, ""); euid = geteuid(); if ((pwd = getpwuid(euid)) != NULL) { smb_ctx_setuser(ctx, pwd->pw_name); endpwent(); } else if (euid == 0) smb_ctx_setuser(ctx, "root"); else return 0; if (argv == NULL) return 0; for (opt = 1; opt < argc; opt++) { cp = argv[opt]; if (strncmp(cp, "//", 2) != 0) continue; error = smb_ctx_parseunc(ctx, cp, sharetype, (const char**)&cp); if (error) return error; ctx->ct_uncnext = cp; break; } while (error == 0 && (opt = cf_getopt(argc, argv, ":E:L:U:")) != -1) { arg = cf_optarg; switch (opt) { case 'E': error = smb_ctx_setcharset(ctx, arg); if (error) return error; break; case 'L': error = nls_setlocale(optarg); if (error) break; break; case 'U': error = smb_ctx_setuser(ctx, arg); break; } } cf_optind = cf_optreset = 1; return error; } void smb_ctx_done(struct smb_ctx *ctx) { 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); if (ctx->ct_srvaddr) free(ctx->ct_srvaddr); if (ctx->ct_nb) nb_ctx_done(ctx->ct_nb); } static int getsubstring(const char *p, u_char 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; } /* * Here we expect something like "[proto:]//[user@]host[/share][/path]" */ int smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, const char **next) { const char *p = unc; char *p1; char tmp[1024]; int error ; ctx->ct_parsedlevel = SMBL_NONE; if (*p++ != '/' || *p++ != '/') { smb_error("UNC should start with '//'", 0); return EINVAL; } p1 = tmp; error = getsubstring(p, '@', p1, sizeof(tmp), &p); if (!error) { if (ctx->ct_maxlevel < SMBL_VC) { smb_error("no user name required", 0); return EINVAL; } if (*p1 == 0) { smb_error("empty user name", 0); return EINVAL; } error = smb_ctx_setuser(ctx, tmp); 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("no server name found", 0); return error; } } if (*p1 == 0) { smb_error("empty server name", 0); return EINVAL; } error = smb_ctx_setserver(ctx, tmp); if (error) return error; if (sharetype == SMB_ST_NONE) { *next = p; return 0; } if (*p != 0 && ctx->ct_maxlevel < SMBL_SHARE) { smb_error("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("unexpected end of line", 0); return error; } } if (*p1 == 0 && ctx->ct_minlevel >= SMBL_SHARE) { smb_error("empty share name", 0); return EINVAL; } *next = p; if (*p1 == 0) return 0; error = smb_ctx_setshare(ctx, 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("invalid local charset specification (%s)", 0, arg); return EINVAL; } scslen = (size_t)strlen(++cp); if (scslen == 0 || scslen >= cslen) { smb_error("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("can't initialize iconv support (%s:%s)", error, localcs, servercs); localcs[0] = 0; servercs[0] = 0; return error; } int smb_ctx_setserver(struct smb_ctx *ctx, const char *name) { if (strlen(name) > SMB_MAXSRVNAMELEN) { smb_error("server name '%s' too long", 0, name); return ENAMETOOLONG; } nls_str_upper(ctx->ct_ssn.ioc_srvname, name); return 0; } int smb_ctx_setuser(struct smb_ctx *ctx, const char *name) { if (strlen(name) > SMB_MAXUSERNAMELEN) { smb_error("user name '%s' too long", 0, name); return ENAMETOOLONG; } nls_str_upper(ctx->ct_ssn.ioc_user, name); return 0; } int smb_ctx_setworkgroup(struct smb_ctx *ctx, const char *name) { if (strlen(name) > SMB_MAXUSERNAMELEN) { smb_error("workgroup name '%s' too long", 0, name); return ENAMETOOLONG; } nls_str_upper(ctx->ct_ssn.ioc_workgroup, name); return 0; } int smb_ctx_setpassword(struct smb_ctx *ctx, const char *passwd) { if (passwd == NULL) return EINVAL; if (strlen(passwd) > SMB_MAXPASSWORDLEN) { smb_error("password too long", 0); return ENAMETOOLONG; } 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); return 0; } int smb_ctx_setshare(struct smb_ctx *ctx, const char *share, int stype) { if (strlen(share) > SMB_MAXSHARENAMELEN) { smb_error("share name '%s' too long", 0, share); return ENAMETOOLONG; } 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 *cp; cp = strchr(pair, ':'); if (cp) { *cp++ = '\0'; if (*cp) { gr = getgrnam(cp); if (gr) { *gid = gr->gr_gid; } else smb_error("Invalid group name %s, ignored", 0, cp); } } if (*pair) { pw = getpwnam(pair); if (pw) { *uid = pw->pw_uid; } else smb_error("Invalid user name %s, ignored", 0, pair); } endpwent(); return 0; } int smb_ctx_opt(struct smb_ctx *ctx, int opt, const char *arg) { int error = 0; char *p, *cp; switch(opt) { case 'U': 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(p, &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': error = smb_ctx_setworkgroup(ctx, arg); break; } return error; } #if 0 static void smb_hexdump(const u_char *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 u_char *tbl) { int error; error = kiconv_add_xlat_table(to, from, tbl); if (error && error != EEXIST) { smb_error("can not setup kernel iconv table (%s:%s)", error, from, to); return error; } 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; u_char cstbl[256]; u_int i; int error = 0; ctx->ct_flags &= ~SMBCF_RESOLVED; if (ssn->ioc_srvname[0] == 0) { smb_error("no server name specified", 0); return EINVAL; } if (ssn->ioc_user[0] == 0) { smb_error("no user name specified for server %s", 0, ssn->ioc_srvname); return EINVAL; } if (ctx->ct_minlevel >= SMBL_SHARE && sh->ioc_share[0] == 0) { smb_error("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 (ctx->ct_srvaddr) { error = nb_resolvehost_in(ctx->ct_srvaddr, &sap); } else { error = nbns_resolvename(ssn->ioc_srvname, ctx->ct_nb, &sap); } if (error) { smb_error("can't get server address", error); return error; } nn.nn_scope = ctx->ct_nb->nb_scope; nn.nn_type = NBT_SERVER; strcpy(nn.nn_name, ssn->ioc_srvname); error = nb_sockaddr(sap, &nn, &saserver); nb_snbfree(sap); if (error) { smb_error("can't allocate server address", error); return error; } ssn->ioc_server = (struct sockaddr*)saserver; if (ctx->ct_locname[0] == 0) { error = nb_getlocalname(ctx->ct_locname); if (error) { smb_error("can't get local name", error); return error; } nls_str_upper(ctx->ct_locname, ctx->ct_locname); } strcpy(nn.nn_name, ctx->ct_locname); 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("can't allocate local address", error); return error; } ssn->ioc_local = (struct sockaddr*)salocal; ssn->ioc_lolen = salocal->snb_len; ssn->ioc_svlen = saserver->snb_len; if (ssn->ioc_password[0] == 0 && (ctx->ct_flags & SMBCF_NOPWD) == 0) { cp = getpass("Password:"); error = smb_ctx_setpassword(ctx, cp); if (error) return error; } ctx->ct_flags |= SMBCF_RESOLVED; return 0; } static int smb_ctx_gethandle(struct smb_ctx *ctx) { int fd, i; char buf[20]; /* * First, try to open as cloned device */ fd = open("/dev/"NSMB_NAME, O_RDWR); if (fd >= 0) { ctx->ct_fd = fd; return 0; } /* * 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) { ctx->ct_fd = fd; return 0; } } /* * This is a compatibility with old /dev/net/nsmb device */ for (i = 0; i < 1024; i++) { snprintf(buf, sizeof(buf), "/dev/net/%s%d", NSMB_NAME, i); fd = open(buf, O_RDWR); if (fd >= 0) { ctx->ct_fd = fd; return 0; } if (errno == ENOENT) return ENOENT; } return ENOENT; } int smb_ctx_lookup(struct smb_ctx *ctx, int level, int flags) { struct smbioc_lookup rq; int error; if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) { smb_error("smb_ctx_lookup() data is not resolved", 0); return EINVAL; } if (ctx->ct_fd != -1) { close(ctx->ct_fd); ctx->ct_fd = -1; } error = smb_ctx_gethandle(ctx); if (error) { smb_error("can't get handle to requester (no /dev/"NSMB_NAME"* device)", 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)); rq.ioc_flags = flags; rq.ioc_level = level; if (ioctl(ctx->ct_fd, SMBIOC_LOOKUP, &rq) == -1) { error = errno; if (flags & SMBLK_CREATE) smb_error("unable to open connection", error); return error; } return 0; } int smb_ctx_login(struct smb_ctx *ctx) { struct smbioc_ossn *ssn = &ctx->ct_ssn; struct smbioc_oshare *sh = &ctx->ct_sh; int error; if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) { smb_error("smb_ctx_resolve() should be called first", 0); return EINVAL; } if (ctx->ct_fd != -1) { close(ctx->ct_fd); ctx->ct_fd = -1; } error = smb_ctx_gethandle(ctx); if (error) { smb_error("can't get handle to requester", 0); return EINVAL; } if (ioctl(ctx->ct_fd, SMBIOC_OPENSESSION, ssn) == -1) { error = errno; smb_error("can't open session to server %s", error, ssn->ioc_srvname); return error; } if (sh->ioc_share[0] == 0) return 0; if (ioctl(ctx->ct_fd, SMBIOC_OPENSHARE, sh) == -1) { error = errno; smb_error("can't connect to share //%s/%s", error, ssn->ioc_srvname, sh->ioc_share); return error; } return 0; } int smb_ctx_setflags(struct smb_ctx *ctx, int level, int mask, int flags) { struct smbioc_flags fl; if (ctx->ct_fd == -1) return EINVAL; fl.ioc_level = level; fl.ioc_mask = mask; fl.ioc_flags = flags; if (ioctl(ctx->ct_fd, SMBIOC_SETFLAGS, &fl) == -1) return errno; return 0; } /* * 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; if (level >= 0) { rc_getstringptr(smb_rc, sname, "charsets", &p); if (p) { error = smb_ctx_setcharset(ctx, p); if (error) smb_error("charset specification in the section '%s' ignored", error, sname); } } if (level <= 1) { rc_getint(smb_rc, sname, "timeout", &ctx->ct_ssn.ioc_timeout); rc_getint(smb_rc, sname, "retry_count", &ctx->ct_ssn.ioc_retrycount); } if (level == 1) { rc_getstringptr(smb_rc, sname, "addr", &p); if (p) { error = smb_ctx_setsrvaddr(ctx, p); if (error) { smb_error("invalid address specified in the section %s", 0, sname); return error; } } } if (level >= 2) { rc_getstringptr(smb_rc, sname, "password", &p); if (p) smb_ctx_setpassword(ctx, p); } rc_getstringptr(smb_rc, sname, "workgroup", &p); if (p) smb_ctx_setworkgroup(ctx, p); return 0; } /* * read rc file as follows: * 1. read [default] section * 2. override with [server] section * 3. override with [server:user:share] section * Since abcence 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]; /* char *p;*/ if (smb_open_rcfile() != 0) return 0; if (ctx->ct_ssn.ioc_user[0] == 0 || ctx->ct_ssn.ioc_srvname[0] == 0) return 0; smb_ctx_readrcsection(ctx, "default", 0); nb_ctx_readrcsection(smb_rc, ctx->ct_nb, "default", 0); smb_ctx_readrcsection(ctx, ctx->ct_ssn.ioc_srvname, 1); nb_ctx_readrcsection(smb_rc, ctx->ct_nb, ctx->ct_ssn.ioc_srvname, 1); /* * 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 (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); } return 0; }