diff options
author | Gordon Ross <Gordon.Ross@Sun.COM> | 2009-07-02 12:58:38 -0400 |
---|---|---|
committer | Gordon Ross <Gordon.Ross@Sun.COM> | 2009-07-02 12:58:38 -0400 |
commit | 613a2f6ba31e891e3d947a356daf5e563d43c1ce (patch) | |
tree | 0f7f3438a5792c05ed156a43e8cd84f25695d7f2 /usr/src/lib/libsmbfs/smb | |
parent | bf73eaa5a8ea69ac16a1e6e7b736f09286d073f9 (diff) | |
download | illumos-joyent-613a2f6ba31e891e3d947a356daf5e563d43c1ce.tar.gz |
6584198 SMB Client needs authentication improvements
6587713 Need to reconnect after server disconnect
--HG--
rename : usr/src/lib/libsmbfs/netsmb/smbfs_isec.h => usr/src/lib/libsmbfs/smb/acl_nt.h
Diffstat (limited to 'usr/src/lib/libsmbfs/smb')
49 files changed, 8037 insertions, 2132 deletions
diff --git a/usr/src/lib/libsmbfs/smb/acl_api.c b/usr/src/lib/libsmbfs/smb/acl_api.c index 6c8552e75a..3e9d703c99 100644 --- a/usr/src/lib/libsmbfs/smb/acl_api.c +++ b/usr/src/lib/libsmbfs/smb/acl_api.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -49,9 +49,11 @@ #include <sys/fs/smbfs_ioctl.h> +#include <netsmb/smb.h> #include <netsmb/smb_lib.h> #include <netsmb/smbfs_acl.h> -#include <netsmb/smbfs_isec.h> + +#include "acl_nt.h" #include "private.h" /* Sanity check SD sizes */ diff --git a/usr/src/lib/libsmbfs/smb/acl_conv.c b/usr/src/lib/libsmbfs/smb/acl_conv.c index ec77c8f3c5..d19b323dfe 100644 --- a/usr/src/lib/libsmbfs/smb/acl_conv.c +++ b/usr/src/lib/libsmbfs/smb/acl_conv.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -54,10 +54,13 @@ #include <sys/fs/smbfs_ioctl.h> +#include <netsmb/mchain.h> +#include <netsmb/smb.h> + #include <netsmb/smb_lib.h> #include <netsmb/smbfs_acl.h> -#include <netsmb/smbfs_isec.h> -#include <netsmb/mchain.h> + +#include "acl_nt.h" #include "private.h" #ifdef _KERNEL @@ -167,7 +170,6 @@ errout: static void ifree_ace(i_ntace_t *ace) { - size_t sz; if (ace == NULL) return; @@ -282,7 +284,6 @@ mb_get_acl(mbdata_t *mbp, i_ntacl_t **aclp) i_ntace_t **acep; uint8_t revision; uint16_t acl_len, acecount; - uint32_t *subauthp; size_t aclsz; int i, error; @@ -327,10 +328,7 @@ static int mb_put_acl(mbdata_t *mbp, i_ntacl_t *acl) { i_ntace_t **acep; - uint8_t revision; uint16_t acl_len, *acl_len_p; - uint32_t *subauthp; - size_t aclsz; int i, cnt0, error; cnt0 = mbp->mb_count; @@ -929,6 +927,7 @@ errout: * Include owner/group too if uid/gid != -1. * Note optional arg: vsa/acl */ +/*ARGSUSED*/ int smbfs_acl_zfs2sd( #ifdef _KERNEL vsecattr_t *vsa, diff --git a/usr/src/lib/libsmbfs/smb/acl_nt.h b/usr/src/lib/libsmbfs/smb/acl_nt.h new file mode 100644 index 0000000000..844a7e6543 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/acl_nt.h @@ -0,0 +1,113 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ACL_NT_H +#define _ACL_NT_H + +/* + * Internal functions for dealing with + * NT Security data structures. + */ + +#include <netsmb/smbfs_acl.h> + +/* + * Internal form of an NT SID + * Same as on the wire, but possibly byte-swapped. + */ +typedef struct i_ntsid { + uint8_t sid_revision; + uint8_t sid_subauthcount; + uint8_t sid_authority[6]; + uint32_t sid_subauthvec[1]; /* actually len=subauthcount */ +} i_ntsid_t; +#define I_SID_SIZE(sacnt) (8 + 4 * (sacnt)) + +/* + * Internal form of an NT ACE + */ +typedef struct i_ntace { + uint8_t ace_type; + uint8_t ace_flags; + uint32_t ace_rights; /* generic, standard, specific, etc */ + i_ntsid_t *ace_sid; +} i_ntace_t; + +/* + * Internal form of an NT ACL (see sacl/dacl below) + */ +typedef struct i_ntacl { + uint8_t acl_revision; /* 0x02 observed with W2K */ + uint16_t acl_acecount; + i_ntace_t *acl_acevec[1]; /* actually, len=acecount */ +} i_ntacl_t; + +/* + * Internal form of an NT Security Descriptor (SD) + */ +struct i_ntsd { + uint8_t sd_revision; /* 0x01 observed between W2K */ + uint16_t sd_flags; + i_ntsid_t *sd_owner; + i_ntsid_t *sd_group; + i_ntacl_t *sd_sacl; + i_ntacl_t *sd_dacl; +}; + +struct mbdata; + +/* + * Import a raw SD (mb chain) into "internal" form. + * (like "absolute" form per. NT docs) + * Returns allocated data in sdp + */ +int mb_get_ntsd(struct mbdata *mbp, i_ntsd_t **sdp); + +/* + * Export an "internal" SD into an raw SD (mb chain). + * (a.k.a "self-relative" form per. NT docs) + * Returns allocated mbchain in mbp. + */ +int mb_put_ntsd(struct mbdata *mbp, i_ntsd_t *sd); + + +/* + * Get an SD via ioctl on FD (with "selector" bits), + * stroing the raw Windows SD in the mb chain mbp. + */ +int smbfs_acl_iocget(int fd, uint32_t selector, struct mbdata *mbp); + +/* + * Set an SD via ioctl on FD (with "selector" bits), + * with a raw Windows SD from the chain mbp. + */ +int smbfs_acl_iocset(int fd, uint32_t selector, struct mbdata *mbp); + + +int smbfs_sid2str(i_ntsid_t *sid, + char *obuf, size_t olen, uint32_t *ridp); + +#endif /* _ACL_NT_H */ diff --git a/usr/src/lib/libsmbfs/smb/acl_print.c b/usr/src/lib/libsmbfs/smb/acl_print.c index 9a1d51e320..259258a9f1 100644 --- a/usr/src/lib/libsmbfs/smb/acl_print.c +++ b/usr/src/lib/libsmbfs/smb/acl_print.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * Print an NT Security Descriptor (SD) and its sub-components. */ @@ -54,7 +52,8 @@ #include <netsmb/smb_lib.h> #include <netsmb/smbfs_acl.h> -#include <netsmb/smbfs_isec.h> + +#include "acl_nt.h" static void fprint_sid(FILE *fp, i_ntsid_t *sid) diff --git a/usr/src/lib/libsmbfs/smb/cfopt.c b/usr/src/lib/libsmbfs/smb/cfopt.c index 094682f6d8..3b2672b2cd 100644 --- a/usr/src/lib/libsmbfs/smb/cfopt.c +++ b/usr/src/lib/libsmbfs/smb/cfopt.c @@ -32,16 +32,19 @@ * $Id: cfopt.c,v 1.1.1.1 2001/06/09 00:28:12 zarzycki Exp $ */ -#pragma ident "%Z%%M% %I% %E% SMI" - -#include <sys/param.h> +#include <sys/types.h> #include <stdio.h> #include <string.h> +#include <synch.h> #include <libintl.h> #include <cflib.h> #include <netsmb/smb_lib.h> +#include <assert.h> + +/* lock for the variables below */ +mutex_t cf_opt_mutex = DEFAULTMUTEX; int cf_opterr = 1, /* if error message should be printed */ cf_optind = 1, /* index into parent argv vector */ @@ -53,6 +56,18 @@ const char *cf_optarg; /* argument associated with option */ #define BADARG (int)':' #define EMSG "" +void +cf_opt_lock(void) +{ + mutex_lock(&cf_opt_mutex); +} + +void +cf_opt_unlock(void) +{ + mutex_unlock(&cf_opt_mutex); +} + int cf_getopt(nargc, nargv, ostr) int nargc; @@ -63,10 +78,12 @@ cf_getopt(nargc, nargv, ostr) char *oli; /* option letter list index */ int tmpind; + assert(MUTEX_HELD(&cf_opt_mutex)); + if (cf_optreset || !*place) { /* update scanning pointer */ cf_optreset = 0; tmpind = cf_optind; - while (1) { + for (;;) { if (tmpind >= nargc) { place = EMSG; return (-1); diff --git a/usr/src/lib/libsmbfs/smb/charsets.c b/usr/src/lib/libsmbfs/smb/charsets.c index 81075a3a2f..1ca0eb751c 100644 --- a/usr/src/lib/libsmbfs/smb/charsets.c +++ b/usr/src/lib/libsmbfs/smb/charsets.c @@ -43,7 +43,9 @@ #include <iconv.h> #include <langinfo.h> #include <strings.h> +#include <libintl.h> +#include <sys/isa_defs.h> #include <netsmb/smb_lib.h> #include <netsmb/mchain.h> diff --git a/usr/src/lib/libsmbfs/smb/charsets.h b/usr/src/lib/libsmbfs/smb/charsets.h index c754425f37..4c917c99d2 100644 --- a/usr/src/lib/libsmbfs/smb/charsets.h +++ b/usr/src/lib/libsmbfs/smb/charsets.h @@ -43,5 +43,9 @@ extern char *convert_leunicode_to_utf8(unsigned short *windows_string); extern char *convert_unicode_to_utf8(unsigned short *windows_string); extern unsigned short *convert_utf8_to_leunicode(const char *utf8_string); extern size_t unicode_strlen(const uint16_t *unicode_string); +extern char *utf8_str_tolower(const char *s); +extern char *utf8_str_toupper(const char *s); + +extern char *unpercent(char *component); #endif /* __CHARSETS_H__ */ diff --git a/usr/src/lib/libsmbfs/smb/connect.c b/usr/src/lib/libsmbfs/smb/connect.c new file mode 100644 index 0000000000..d0f4e2b228 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/connect.c @@ -0,0 +1,535 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Functions to setup connections (TCP and/or NetBIOS) + * This has the fall-back logic for IP6, IP4, NBT + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +/* + * SMB messages are up to 64K. + * Let's leave room for two. + */ +static int smb_tcpsndbuf = 0x20000; +static int smb_tcprcvbuf = 0x20000; +static int smb_connect_timeout = 30; /* seconds */ +int smb_recv_timeout = 30; /* seconds */ + +int conn_tcp6(struct smb_ctx *, const struct sockaddr *, int); +int conn_tcp4(struct smb_ctx *, const struct sockaddr *, int); +int conn_nbt(struct smb_ctx *, const struct sockaddr *, char *); + +/* + * Internal set sockopt for int-sized options. + * Borrowed from: libnsl/rpc/ti_opts.c + */ +static int +smb_setopt_int(int fd, int level, int name, int val) +{ + struct t_optmgmt oreq, ores; + struct { + struct t_opthdr oh; + int ival; + } opts; + + /* opt header */ + opts.oh.len = sizeof (opts); + opts.oh.level = level; + opts.oh.name = name; + opts.oh.status = 0; + opts.ival = val; + + oreq.flags = T_NEGOTIATE; + oreq.opt.buf = (void *)&opts; + oreq.opt.len = sizeof (opts); + + ores.flags = 0; + ores.opt.buf = NULL; + ores.opt.maxlen = 0; + + if (t_optmgmt(fd, &oreq, &ores) < 0) { + DPRINT("t_opgmgnt, t_errno = %d", t_errno); + if (t_errno == TSYSERR) + return (errno); + return (EPROTO); + } + if (ores.flags != T_SUCCESS) { + DPRINT("flags 0x%x, status 0x%x", + (int)ores.flags, (int)opts.oh.status); + return (EPROTO); + } + + return (0); +} + +static int +smb_setopts(int fd) +{ + int err; + + /* + * Set various socket/TCP options. + * Failures here are not fatal - + * just log a complaint. + * + * We don't need these two: + * SO_RCVTIMEO, SO_SNDTIMEO + */ + + err = smb_setopt_int(fd, SOL_SOCKET, SO_SNDBUF, smb_tcpsndbuf); + if (err) { + DPRINT("set SO_SNDBUF, err %d", err); + } + + err = smb_setopt_int(fd, SOL_SOCKET, SO_RCVBUF, smb_tcprcvbuf); + if (err) { + DPRINT("set SO_RCVBUF, err %d", err); + } + + err = smb_setopt_int(fd, SOL_SOCKET, SO_KEEPALIVE, 1); + if (err) { + DPRINT("set SO_KEEPALIVE, err %d", err); + } + + err = smb_setopt_int(fd, IPPROTO_TCP, TCP_NODELAY, 1); + if (err) { + DPRINT("set TCP_NODELAY, err %d", err); + } + + /* Set the connect timeout (in milliseconds). */ + err = smb_setopt_int(fd, IPPROTO_TCP, + TCP_CONN_ABORT_THRESHOLD, + smb_connect_timeout * 1000); + if (err) { + DPRINT("set connect timeout, err %d", err); + } + return (0); +} + + +int +conn_tcp6(struct smb_ctx *ctx, const struct sockaddr *sa, int port) +{ + struct sockaddr_in6 sin6; + char *dev = "/dev/tcp6"; + char paddrbuf[INET6_ADDRSTRLEN]; + struct t_call sndcall; + int fd, err; + + if (sa->sa_family != AF_INET6) { + DPRINT("bad af %d", sa->sa_family); + return (EINVAL); + } + bcopy(sa, &sin6, sizeof (sin6)); + sin6.sin6_port = htons(port); + + DPRINT("tcp6: %s (%d)", + inet_ntop(AF_INET6, &sin6.sin6_addr, + paddrbuf, sizeof (paddrbuf)), port); + + fd = t_open(dev, O_RDWR, NULL); + if (fd < 0) { + /* Assume t_errno = TSYSERR */ + err = errno; + perror(dev); + return (err); + } + if ((err = smb_setopts(fd)) != 0) + goto errout; + if (t_bind(fd, NULL, NULL) < 0) { + DPRINT("t_bind t_errno %d", t_errno); + if (t_errno == TSYSERR) + err = errno; + else + err = EPROTO; + goto errout; + } + sndcall.addr.maxlen = sizeof (sin6); + sndcall.addr.len = sizeof (sin6); + sndcall.addr.buf = (void *) &sin6; + sndcall.opt.len = 0; + sndcall.udata.len = 0; + if (t_connect(fd, &sndcall, NULL) < 0) { + err = get_xti_err(fd); + DPRINT("connect, err %d", err); + goto errout; + } + + DPRINT("tcp6: connected, fd=%d", fd); + ctx->ct_tran_fd = fd; + return (0); + +errout: + close(fd); + return (err); +} + +/* + * This is used for both SMB over TCP (port 445) + * and NetBIOS - see conn_nbt(). + */ +int +conn_tcp4(struct smb_ctx *ctx, const struct sockaddr *sa, int port) +{ + struct sockaddr_in sin; + char *dev = "/dev/tcp"; + char paddrbuf[INET_ADDRSTRLEN]; + struct t_call sndcall; + int fd, err; + + if (sa->sa_family != AF_INET) { + DPRINT("bad af %d", sa->sa_family); + return (EINVAL); + } + bcopy(sa, &sin, sizeof (sin)); + sin.sin_port = htons(port); + + DPRINT("tcp4: %s (%d)", + inet_ntop(AF_INET, &sin.sin_addr, + paddrbuf, sizeof (paddrbuf)), port); + + fd = t_open(dev, O_RDWR, NULL); + if (fd < 0) { + /* Assume t_errno = TSYSERR */ + err = errno; + perror(dev); + return (err); + } + if ((err = smb_setopts(fd)) != 0) + goto errout; + if (t_bind(fd, NULL, NULL) < 0) { + DPRINT("t_bind t_errno %d", t_errno); + if (t_errno == TSYSERR) + err = errno; + else + err = EPROTO; + goto errout; + } + sndcall.addr.maxlen = sizeof (sin); + sndcall.addr.len = sizeof (sin); + sndcall.addr.buf = (void *) &sin; + sndcall.opt.len = 0; + sndcall.udata.len = 0; + if (t_connect(fd, &sndcall, NULL) < 0) { + err = get_xti_err(fd); + DPRINT("connect, err %d", err); + goto errout; + } + + DPRINT("tcp4: connected, fd=%d", fd); + ctx->ct_tran_fd = fd; + return (0); + +errout: + close(fd); + return (err); +} + +/* + * Open a NetBIOS connection (session, port 139) + * + * The optional name parameter, if passed, means + * we found the sockaddr via NetBIOS name lookup, + * and can just use that for our session request. + * Otherwise (if name is NULL), we're connecting + * by IP address, and need to come up with the + * NetBIOS name by other means. + */ +int +conn_nbt(struct smb_ctx *ctx, const struct sockaddr *saarg, char *name) +{ + struct sockaddr_in sin; + struct sockaddr *sa; + char server[NB_NAMELEN]; + char workgroup[NB_NAMELEN]; + int err, nberr, port; + + bcopy(saarg, &sin, sizeof (sin)); + sa = (struct sockaddr *)&sin; + + switch (sin.sin_family) { + case AF_NETBIOS: /* our fake AF */ + sin.sin_family = AF_INET; + break; + case AF_INET: + break; + default: + DPRINT("bad af %d", sin.sin_family); + return (EINVAL); + } + port = IPPORT_NETBIOS_SSN; + + /* + * If we have a NetBIOS name, just use it. + * This is the path taken when we've done a + * NetBIOS name lookup on this name to get + * the IP address in the passed sa. Otherwise, + * we're connecting by IP address, and need to + * figure out what NetBIOS name to use. + */ + if (name) { + strlcpy(server, name, sizeof (server)); + DPRINT("given name: %s", server); + } else { + /* + * + * Try a NetBIOS node status query, + * which searches for a type=[20] name. + * If that doesn't work, just use the + * (fake) "*SMBSERVER" name. + */ + DPRINT("try node status"); + server[0] = '\0'; + nberr = nbns_getnodestatus(ctx->ct_nb, + &sin.sin_addr, server, workgroup); + if (nberr == 0 && server[0] != '\0') { + /* Found the name. Save for reconnect. */ + DPRINT("found name: %s", server); + strlcpy(ctx->ct_srvname, server, + sizeof (ctx->ct_srvname)); + } else { + DPRINT("getnodestatus, nberr %d", nberr); + strlcpy(server, "*SMBSERVER", sizeof (server)); + } + } + + /* + * Establish the TCP connection. + * Careful to close it on errors. + */ + if ((err = conn_tcp4(ctx, sa, port)) != 0) { + DPRINT("TCP connect: err=%d", err); + goto out; + } + + /* Connected. Do NetBIOS session request. */ + err = nb_ssn_request(ctx, server); + if (err) + DPRINT("ssn_rq, err %d", err); + +out: + if (err) { + if (ctx->ct_tran_fd != -1) { + close(ctx->ct_tran_fd); + ctx->ct_tran_fd = -1; + } + } + return (err); +} + +/* + * Make a new connection, or reconnect. + */ +int +smb_iod_connect(smb_ctx_t *ctx) +{ + struct sockaddr *sa; + int err, err2; + struct mbdata blob; + + memset(&blob, 0, sizeof (blob)); + + if (ctx->ct_srvname[0] == '\0') { + DPRINT("sername not set!"); + return (EINVAL); + } + DPRINT("server: %s", ctx->ct_srvname); + + if (smb_debug) + dump_ctx("smb_iod_connect", ctx); + + /* + * This may be a reconnect, so + * cleanup if necessary. + */ + if (ctx->ct_tran_fd != -1) { + close(ctx->ct_tran_fd); + ctx->ct_tran_fd = -1; + } + + /* + * Get local machine name. + * Full name - not a NetBIOS name. + */ + if (ctx->ct_locname == NULL) { + err = smb_getlocalname(&ctx->ct_locname); + if (err) { + smb_error(dgettext(TEXT_DOMAIN, + "can't get local name"), err); + return (err); + } + } + + /* + * We're called with each IP address + * already copied into ct_srvaddr. + */ + ctx->ct_flags |= SMBCF_RESOLVED; + + sa = &ctx->ct_srvaddr.sa; + switch (sa->sa_family) { + + case AF_INET6: + err = conn_tcp6(ctx, sa, IPPORT_SMB); + break; + + case AF_INET: + err = conn_tcp4(ctx, sa, IPPORT_SMB); + /* + * If port 445 was not listening, try port 139. + * Note: Not doing NetBIOS name lookup here. + * We already have the IP address. + */ + switch (err) { + case ECONNRESET: + case ECONNREFUSED: + err2 = conn_nbt(ctx, sa, NULL); + if (err2 == 0) + err = 0; + } + break; + + case AF_NETBIOS: + /* Like AF_INET, but use NetBIOS ssn. */ + err = conn_nbt(ctx, sa, ctx->ct_srvname); + break; + + default: + DPRINT("skipped family %d", sa->sa_family); + err = EPROTONOSUPPORT; + break; + } + + + if (err) { + DPRINT("connect, err=%d", err); + return (err); + } + + /* + * SMB Negotiate Protocol and + * SMB Session Setup, one of 3 ways: + * NULL session + * Extended security, + * NTLM (v2, v1) + * + * Empty user name means an explicit request for + * NULL session setup. No fall-back logic here. + * + * For NULL session, don't offer extended security. + * That's a lot simpler than dealing with NTLMSSP. + */ + if (ctx->ct_user[0] == '\0') { + ctx->ct_vopt &= ~SMBVOPT_EXT_SEC; + err = smb_negprot(ctx, &blob); + if (err) + goto out; + err = smb_ssnsetup_null(ctx); + } else { + /* + * Do SMB Negotiate Protocol. + */ + err = smb_negprot(ctx, &blob); + if (err) + goto out; + + /* + * Do SMB Session Setup (authenticate) + * + * If the server negotiated extended security, + * run the SPNEGO state machine. + */ + if (ctx->ct_sopt.sv_caps & SMB_CAP_EXT_SECURITY) { + err = smb_ssnsetup_spnego(ctx, &blob); + } else { + /* + * Server did NOT negotiate extended security. + * Try NTLMv2, NTLMv1 (if enabled). + */ + if ((ctx->ct_authflags & + (SMB_AT_NTLM2 | SMB_AT_NTLM1)) == 0) { + /* + * Don't return EAUTH, because a + * new password will not help. + */ + DPRINT("No NTLM authflags"); + err = ENOTSUP; + goto out; + } + if (ctx->ct_authflags & SMB_AT_NTLM2) + err = smb_ssnsetup_ntlm2(ctx); + else + err = EAUTH; + if (err == EAUTH && 0 != + (ctx->ct_authflags & SMB_AT_NTLM1)) + err = smb_ssnsetup_ntlm1(ctx); + } + } + + /* Tell library code we have a session. */ + ctx->ct_flags |= SMBCF_RESOLVED | SMBCF_SSNACTIVE; + +out: + mb_done(&blob); + + if (err) { + close(ctx->ct_tran_fd); + ctx->ct_tran_fd = -1; + } else + DPRINT("tran_fd = %d", ctx->ct_tran_fd); + + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/crypt.c b/usr/src/lib/libsmbfs/smb/crypt.c new file mode 100644 index 0000000000..ea1d7e6dd1 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/crypt.c @@ -0,0 +1,175 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Crypto support, using libpkcs11 + * + * Some code copied from the server: libsmb smb_crypt.c + * with minor changes, i.e. errno.h return values. + * XXX: Move this to a common library (later). + */ + +#include <sys/types.h> +#include <sys/md4.h> + +#include <errno.h> +#include <fcntl.h> +#include <string.h> + +#include <security/cryptoki.h> +#include <security/pkcs11.h> +#include <cryptoutil.h> + +#include "smb_crypt.h" + +static void +smb_initlmkey(uchar_t *keyout, const uchar_t *keyin); + +/* + * Like libsmb smb_auth_DES, + * but use uchar_t, return errno. + */ +int +smb_encrypt_DES(uchar_t *Result, int ResultLen, + const uchar_t *Key, int KeyLen, + const uchar_t *Data, int DataLen) +{ + CK_RV rv; + CK_MECHANISM mechanism; + CK_OBJECT_HANDLE hKey; + CK_SESSION_HANDLE hSession; + CK_ULONG ciphertext_len; + uchar_t des_key[8]; + int error = 0; + int K, D; + int k, d; + + /* Calculate proper number of iterations */ + K = KeyLen / 7; + D = DataLen / 8; + + if (ResultLen < (K * 8 * D)) { + return (EINVAL); + } + + /* + * Use SUNW convenience function to initialize the cryptoki + * library, and open a session with a slot that supports + * the mechanism we plan on using. + */ + mechanism.mechanism = CKM_DES_ECB; + mechanism.pParameter = NULL; + mechanism.ulParameterLen = 0; + rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); + if (rv != CKR_OK) { + return (ENOTSUP); + } + + for (k = 0; k < K; k++) { + smb_initlmkey(des_key, &Key[k * 7]); + rv = SUNW_C_KeyToObject(hSession, mechanism.mechanism, + des_key, 8, &hKey); + if (rv != CKR_OK) { + error = EIO; + goto exit_session; + } + /* Initialize the encryption operation in the session */ + rv = C_EncryptInit(hSession, &mechanism, hKey); + if (rv != CKR_OK) { + error = EIO; + goto exit_encrypt; + } + ciphertext_len = DataLen; + for (d = 0; d < D; d++) { + /* Read in the data and encrypt this portion */ + rv = C_EncryptUpdate(hSession, + (CK_BYTE_PTR)Data + (d * 8), 8, + &Result[(k * (8 * D)) + (d * 8)], + &ciphertext_len); + if (rv != CKR_OK) { + error = EIO; + goto exit_encrypt; + } + } + (void) C_DestroyObject(hSession, hKey); + } + goto exit_session; + +exit_encrypt: + (void) C_DestroyObject(hSession, hKey); +exit_session: + (void) C_CloseSession(hSession); + + return (error); +} + +/* + * See "Netlogon Credential Computation" section of MS-NRPC document. + * Same as in libsmb, but output arg first. + */ +static void +smb_initlmkey(uchar_t *keyout, const uchar_t *keyin) +{ + int i; + + keyout[0] = keyin[0] >> 0x01; + keyout[1] = ((keyin[0] & 0x01) << 6) | (keyin[1] >> 2); + keyout[2] = ((keyin[1] & 0x03) << 5) | (keyin[2] >> 3); + keyout[3] = ((keyin[2] & 0x07) << 4) | (keyin[3] >> 4); + keyout[4] = ((keyin[3] & 0x0f) << 3) | (keyin[4] >> 5); + keyout[5] = ((keyin[4] & 0x1f) << 2) | (keyin[5] >> 6); + keyout[6] = ((keyin[5] & 0x3f) << 1) | (keyin[6] >> 7); + keyout[7] = keyin[6] & 0x7f; + + for (i = 0; i < 8; i++) + keyout[i] = (keyout[i] << 1) & 0xfe; +} + +/* + * Get some random bytes from /dev/urandom + * + * There may be a preferred way to call this via libpkcs11 + * XXX: (see: C_GenerateRandom, etc. -- later...) + * Just read from /dev/urandom for now. + */ +int +smb_get_urandom(void *data, size_t dlen) +{ + int fd, rlen; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return (errno); + + rlen = read(fd, data, dlen); + close(fd); + + if (rlen < 0) + return (errno); + if (rlen < dlen) + return (EIO); + return (0); +} diff --git a/usr/src/lib/libsmbfs/smb/ctx.c b/usr/src/lib/libsmbfs/smb/ctx.c index dfc86bd191..87f069402c 100644 --- a/usr/src/lib/libsmbfs/smb/ctx.c +++ b/usr/src/lib/libsmbfs/smb/ctx.c @@ -33,7 +33,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -58,28 +58,35 @@ #include <assert.h> #include <nss_dbdefs.h> -#include <kerberosv5/krb5.h> -#include <kerberosv5/com_err.h> - +#include <cflib.h> #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 "charsets.h" +#include "spnego.h" #include "derparse.h" #include "private.h" +#include "ntlm.h" -extern MECH_OID g_stcMechOIDList []; +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif -#define POWEROF2(x) (((x) & ((x)-1)) == 0) /* These two may be set by commands. */ int smb_debug, smb_verbose; /* + * Was: STDPARAM_OPT - see smb_ctx_scan_argv, smb_ctx_opt + */ +const char smbutil_std_opts[] = "ABCD:E:I:L:M:NO:P:U:R:S:T:W:"; + +/* * Give the RPC library a callback hook that will be * called whenever we destroy or reinit an smb_ctx_t. * The name rpc_cleanup_smbctx() is legacy, and was @@ -138,21 +145,30 @@ dump_ctx_flags(int flags) } void -dump_ctx_ssn(struct smbioc_ossn *ssn) +dump_iod_ssn(smb_iod_ssn_t *is) { - 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); -} + static const char zeros[NTLM_HASH_SZ] = {0}; + struct smbioc_ossn *ssn = &is->iod_ossn; + + printf(" ct_srvname=\"%s\", ", ssn->ssn_srvname); + dump_sockaddr(&ssn->ssn_srvaddr.sa); + printf(" dom=\"%s\", user=\"%s\"\n", + ssn->ssn_domain, ssn->ssn_user); + printf(" ct_vopt=0x%x, ct_owner=%d\n", + ssn->ssn_vopt, ssn->ssn_owner); + printf(" ct_authflags=0x%x\n", is->iod_authflags); + + printf(" ct_nthash:"); + if (bcmp(zeros, &is->iod_nthash, NTLM_HASH_SZ)) + smb_hexdump(&is->iod_nthash, NTLM_HASH_SZ); + else + printf(" {0}\n"); -void -dump_ctx_sh(struct smbioc_oshare *sh) -{ - printf(" share_name=\"%s\", share_pw=\"%s\"\n", - sh->ioc_share, sh->ioc_password); + printf(" ct_lmhash:"); + if (bcmp(zeros, &is->iod_lmhash, NTLM_HASH_SZ)) + smb_hexdump(&is->iod_lmhash, NTLM_HASH_SZ); + else + printf(" {0}\n"); } void @@ -161,24 +177,105 @@ 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_locname) + printf(" localname=\"%s\"", ctx->ct_locname); + else + printf(" localname=NULL"); if (ctx->ct_fullserver) printf(" fullserver=\"%s\"", ctx->ct_fullserver); else printf(" fullserver=NULL"); - if (ctx->ct_srvaddr) - printf(" srvaddr=\"%s\"\n", ctx->ct_srvaddr); + if (ctx->ct_srvaddr_s) + printf(" srvaddr_s=\"%s\"\n", ctx->ct_srvaddr_s); + else + printf(" srvaddr_s=NULL\n"); + + if (ctx->ct_addrinfo) + dump_addrinfo(ctx->ct_addrinfo); else - printf(" srvaddr=NULL\n"); + printf(" ct_addrinfo = NULL\n"); + + dump_iod_ssn(&ctx->ct_iod_ssn); + + printf(" share_name=\"%s\", share_type=%d\n", + ctx->ct_origshare ? ctx->ct_origshare : "", + ctx->ct_shtype_req); - dump_ctx_ssn(&ctx->ct_ssn); - dump_ctx_sh(&ctx->ct_sh); + /* dump_iod_work()? */ +} + +int +smb_ctx_alloc(struct smb_ctx **ctx_pp) +{ + smb_ctx_t *ctx; + int err; + + ctx = malloc(sizeof (*ctx)); + if (ctx == NULL) + return (ENOMEM); + err = smb_ctx_init(ctx); + if (err != 0) { + free(ctx); + return (err); + } + *ctx_pp = ctx; + return (0); } /* - * Initialize an smb_ctx struct. + * Initialize an smb_ctx struct (defaults) + */ +int +smb_ctx_init(struct smb_ctx *ctx) +{ + char pwbuf[NSS_BUFLEN_PASSWD]; + struct passwd pw; + int error = 0; + + bzero(ctx, sizeof (*ctx)); + + error = nb_ctx_create(&ctx->ct_nb); + if (error) + return (error); + + ctx->ct_dev_fd = -1; + ctx->ct_tran_fd = -1; + ctx->ct_parsedlevel = SMBL_NONE; + ctx->ct_minlevel = SMBL_NONE; + ctx->ct_maxlevel = SMBL_PATH; + + /* Fill in defaults */ + ctx->ct_vopt = SMBVOPT_EXT_SEC; + ctx->ct_owner = SMBM_ANY_OWNER; + ctx->ct_authflags = SMB_AT_DEFAULT; + ctx->ct_minauth = SMB_AT_DEFAULT; + + 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(getuid(), &pw, pwbuf, sizeof (pwbuf)) != NULL) { + smb_ctx_setuser(ctx, pw.pw_name, 0); + ctx->ct_home = strdup(pw.pw_name); + } + + /* + * Set a built-in default domain (workgroup). + * Using the Windows/NT default for now. + */ + smb_ctx_setdomain(ctx, "WORKGROUP", 0); + + return (error); +} + +/* + * "Scan" the command line args to find the server name, + * user name, and share name, as needed. We need these + * before reading the RC files and/or sharectl values. * * The sequence for getting all the members filled in * has some tricky aspects. Here's how it works: @@ -211,154 +308,137 @@ dump_ctx(char *where, struct smb_ctx *ctx) * ignore options not in the options string. */ int -smb_ctx_init(struct smb_ctx *ctx, int argc, char *argv[], +smb_ctx_scan_argv(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 ind, opt, error = 0; 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; - - /* Fill in defaults */ - 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; - } + const char *arg; /* * 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) { + if (argv == NULL) + return (0); + + ctx->ct_minlevel = minlevel; + ctx->ct_maxlevel = maxlevel; + ctx->ct_shtype_req = sharetype; + + cf_opt_lock(); + /* Careful: no return/goto before cf_opt_unlock! */ + while (error == 0) { + opt = cf_getopt(argc, argv, STDPARAM_OPT); + if (opt == -1) + break; arg = cf_optarg; + /* NB: handle most in smb_ctx_opt */ switch (opt) { case 'A': aflg = 1; error = smb_ctx_setuser(ctx, "", TRUE); - 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; + default: + DPRINT("skip opt=%c", opt); + break; } } + ind = cf_optind; + arg = argv[ind]; + cf_optind = cf_optreset = 1; + cf_opt_unlock(); + + if (error) + return (error); + if (aflg && uflg) { printf(gettext("-A and -U flags are exclusive.\n")); - return (1); + return (EINVAL); } - cf_optind = cf_optreset = 1; -done: - if (smb_debug) - dump_ctx("after smb_ctx_init", ctx); + /* + * Parse the UNC path. Values from here are + * marked as "from CMD". + */ + for (; ind < argc; ind++) { + arg = argv[ind]; + if (strncmp(arg, "//", 2) != 0) + continue; + error = smb_ctx_parseunc(ctx, arg, + minlevel, maxlevel, sharetype, &arg); + if (error) + return (error); + break; + } return (error); } void +smb_ctx_free(smb_ctx_t *ctx) +{ + smb_ctx_done(ctx); + free(ctx); +} + +void smb_ctx_done(struct smb_ctx *ctx) { rpc_cleanup_smbctx(ctx); - /* 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_dev_fd != -1) { + close(ctx->ct_dev_fd); + ctx->ct_dev_fd = -1; } - - 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) + if (ctx->ct_tran_fd != -1) { + close(ctx->ct_tran_fd); + ctx->ct_tran_fd = -1; + } + if (ctx->ct_srvaddr_s) { + free(ctx->ct_srvaddr_s); + ctx->ct_srvaddr_s = NULL; + } + if (ctx->ct_nb) { nb_ctx_done(ctx->ct_nb); - if (ctx->ct_secblob) - free(ctx->ct_secblob); - if (ctx->ct_origshare) + ctx->ct_nb = NULL; + } + if (ctx->ct_locname) { + free(ctx->ct_locname); + ctx->ct_locname = NULL; + } + if (ctx->ct_origshare) { free(ctx->ct_origshare); - if (ctx->ct_fullserver) + ctx->ct_origshare = NULL; + } + if (ctx->ct_fullserver) { free(ctx->ct_fullserver); + ctx->ct_fullserver = NULL; + } + if (ctx->ct_addrinfo) { + freeaddrinfo(ctx->ct_addrinfo); + ctx->ct_addrinfo = NULL; + } + if (ctx->ct_home) + free(ctx->ct_home); + if (ctx->ct_srv_OS) { + free(ctx->ct_srv_OS); + ctx->ct_srv_OS = NULL; + } + if (ctx->ct_srv_LM) { + free(ctx->ct_srv_LM); + ctx->ct_srv_LM = NULL; + } + if (ctx->ct_mackey) { + free(ctx->ct_mackey); + ctx->ct_mackey = NULL; + } } static int @@ -385,20 +465,30 @@ getsubstring(const char *p, uchar_t sep, char *dest, int maxlen, * Values found here are marked as "from CMD". */ int -smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, +smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, + int minlevel, int maxlevel, int sharetype, const char **next) { const char *p = unc; - char *p1, *colon, *servername; + char *p1, *colon; char tmp[1024]; char tmp2[1024]; int error; + /* + * This may be called outside of _scan_argv, + * so make sure these get initialized. + */ + ctx->ct_minlevel = minlevel; + ctx->ct_maxlevel = maxlevel; + ctx->ct_shtype_req = sharetype; + ctx->ct_parsedlevel = SMBL_NONE; if (*p++ != '/' || *p++ != '/') { smb_error(dgettext(TEXT_DOMAIN, "UNC should start with '//'"), 0); - return (EINVAL); + error = EINVAL; + goto out; } p1 = tmp; error = getsubstring(p, ';', p1, sizeof (tmp), &p); @@ -406,12 +496,13 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty workgroup name"), 0); - return (EINVAL); + error = EINVAL; + goto out; } nls_str_upper(tmp, tmp); - error = smb_ctx_setworkgroup(ctx, unpercent(tmp), TRUE); + error = smb_ctx_setdomain(ctx, unpercent(tmp), TRUE); if (error) - return (error); + goto out; } colon = (char *)p; error = getsubstring(p, '@', p1, sizeof (tmp), &p); @@ -419,7 +510,8 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, if (ctx->ct_maxlevel < SMBL_VC) { smb_error(dgettext(TEXT_DOMAIN, "no user name required"), 0); - return (EINVAL); + error = EINVAL; + goto out; } p1 = strchr(tmp, ':'); if (p1) { @@ -427,7 +519,7 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, *p1++ = (char)0; error = smb_ctx_setpassword(ctx, unpercent(p1), TRUE); if (error) - return (error); + goto out; if (p - colon > 2) memset(colon+1, '*', p - colon - 2); } @@ -435,11 +527,12 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty user name"), 0); - return (EINVAL); + error = EINVAL; + goto out; } error = smb_ctx_setuser(ctx, unpercent(tmp), TRUE); if (error) - return (error); + goto out; ctx->ct_parsedlevel = SMBL_VC; } error = getsubstring(p, '/', p1, sizeof (tmp), &p); @@ -448,15 +541,15 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, if (error) { smb_error(dgettext(TEXT_DOMAIN, "no server name found"), 0); - return (error); + goto out; } } if (*p1 == 0) { smb_error(dgettext(TEXT_DOMAIN, "empty server name"), 0); - return (EINVAL); + error = EINVAL; + goto out; } - /* * It's safe to uppercase this string, which * consists of ascii characters that should @@ -464,49 +557,32 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, * 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! + * "unpercent" converted, below! */ - nls_str_upper(tmp2, tmp); /* - * scan for % in the string. - * If we find one, convert - * to the assumed codepage. + * Save ct_fullserver without case conversion. */ - - 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 (strchr(tmp, '%')) + (void) unpercent(tmp); + smb_ctx_setfullserver(ctx, tmp); if (error) - return (error); + goto out; + +#ifdef SMB_ST_NONE if (sharetype == SMB_ST_NONE) { - *next = p; - return (0); + if (next) + *next = p; + error = 0; + goto out; } +#endif + if (*p != 0 && ctx->ct_maxlevel < SMBL_SHARE) { smb_error(dgettext(TEXT_DOMAIN, "no share name required"), 0); - return (EINVAL); + error = EINVAL; + goto out; } error = getsubstring(p, '/', p1, sizeof (tmp), &p); if (error) { @@ -514,21 +590,31 @@ smb_ctx_parseunc(struct smb_ctx *ctx, const char *unc, int sharetype, if (error) { smb_error(dgettext(TEXT_DOMAIN, "unexpected end of line"), 0); - return (error); + goto out; } } if (*p1 == 0 && ctx->ct_minlevel >= SMBL_SHARE && !(ctx->ct_flags & SMBCF_BROWSEOK)) { smb_error(dgettext(TEXT_DOMAIN, "empty share name"), 0); - return (EINVAL); + error = EINVAL; + goto out; + } + if (next) + *next = p; + if (*p1 == 0) { + error = 0; + goto out; } - *next = p; - if (*p1 == 0) - return (0); error = smb_ctx_setshare(ctx, unpercent(p1), sharetype); + +out: + if (error == 0 && smb_debug > 0) + dump_ctx("after smb_ctx_parseunc", ctx); + return (error); } +#ifdef KICONV_SUPPORT int smb_ctx_setcharset(struct smb_ctx *ctx, const char *arg) { @@ -562,98 +648,43 @@ smb_ctx_setcharset(struct smb_ctx *ctx, const char *arg) servercs[0] = 0; return (error); } +#endif /* KICONV_SUPPORT */ int -smb_ctx_setfullserver(struct smb_ctx *ctx, const char *name) +smb_ctx_setauthflags(struct smb_ctx *ctx, int flags) { - ctx->ct_fullserver = strdup(name); - if (ctx->ct_fullserver == NULL) - return (ENOMEM); + ctx->ct_authflags = flags; 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) +smb_ctx_setfullserver(struct smb_ctx *ctx, const char *name) { - char server[SMB_MAXSRVNAMELEN + 1]; - char workgroup[SMB_MAXUSERNAMELEN + 1]; - int error; -#if 0 - char *dot; -#endif + char *p = strdup(name); - 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); + if (p == NULL) + return (ENOMEM); + if (ctx->ct_fullserver) + free(ctx->ct_fullserver); + ctx->ct_fullserver = p; + return (0); } /* this routine does not uppercase the server name */ -void +int 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); + strlcpy(ctx->ct_srvname, name, + sizeof (ctx->ct_srvname)); + return (0); } int smb_ctx_setuser(struct smb_ctx *ctx, const char *name, int from_cmd) { - if (strlen(name) >= SMB_MAXUSERNAMELEN) { + if (strlen(name) >= sizeof (ctx->ct_user)) { smb_error(dgettext(TEXT_DOMAIN, "user name '%s' too long"), 0, name); return (ENAMETOOLONG); @@ -667,7 +698,8 @@ smb_ctx_setuser(struct smb_ctx *ctx, const char *name, int from_cmd) return (0); /* don't uppercase the username, just copy it. */ - strcpy(ctx->ct_ssn.ioc_user, name); + strlcpy(ctx->ct_user, name, + sizeof (ctx->ct_user)); /* Mark this as "from the command line". */ if (from_cmd) @@ -686,10 +718,10 @@ smb_ctx_setuser(struct smb_ctx *ctx, const char *name, int from_cmd) * See smb_ctx_init() for notes about this. */ int -smb_ctx_setworkgroup(struct smb_ctx *ctx, const char *name, int from_cmd) +smb_ctx_setdomain(struct smb_ctx *ctx, const char *name, int from_cmd) { - if (strlen(name) >= SMB_MAXUSERNAMELEN) { + if (strlen(name) >= sizeof (ctx->ct_domain)) { smb_error(dgettext(TEXT_DOMAIN, "workgroup name '%s' too long"), 0, name); return (ENAMETOOLONG); @@ -702,7 +734,8 @@ smb_ctx_setworkgroup(struct smb_ctx *ctx, const char *name, int from_cmd) if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_DOM)) return (0); - strcpy(ctx->ct_ssn.ioc_workgroup, name); + strlcpy(ctx->ct_domain, name, + sizeof (ctx->ct_domain)); /* Mark this as "from the command line". */ if (from_cmd) @@ -714,26 +747,41 @@ smb_ctx_setworkgroup(struct smb_ctx *ctx, const char *name, int from_cmd) int smb_ctx_setpassword(struct smb_ctx *ctx, const char *passwd, int from_cmd) { + int err; - if (passwd == NULL) /* XXX Huh? */ + if (passwd == NULL) return (EINVAL); - if (strlen(passwd) >= SMB_MAXPASSWORDLEN) { + if (strlen(passwd) >= sizeof (ctx->ct_password)) { 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 called again after comand line parsing, + * don't overwrite a value from the command line + * with one from any stored config. */ if (!from_cmd && (ctx->ct_flags & SMBCF_CMD_PW)) return (0); + memset(ctx->ct_password, 0, sizeof (ctx->ct_password)); if (strncmp(passwd, "$$1", 3) == 0) - smb_simpledecrypt(ctx->ct_ssn.ioc_password, passwd); + smb_simpledecrypt(ctx->ct_password, passwd); else - strcpy(ctx->ct_ssn.ioc_password, passwd); - strcpy(ctx->ct_sh.ioc_password, ctx->ct_ssn.ioc_password); + strlcpy(ctx->ct_password, passwd, + sizeof (ctx->ct_password)); + + /* + * Compute LM hash, NT hash. + */ + if (ctx->ct_password[0]) { + err = ntlm_compute_nt_hash(ctx->ct_nthash, ctx->ct_password); + if (err != 0) + return (err); + err = ntlm_compute_lm_hash(ctx->ct_lmhash, ctx->ct_password); + if (err != 0) + return (err); + } /* Mark this as "from the command line". */ if (from_cmd) @@ -742,10 +790,37 @@ smb_ctx_setpassword(struct smb_ctx *ctx, const char *passwd, int from_cmd) return (0); } +/* + * Use this to set NTLM auth. info (hashes) + * when we don't have the password. + */ +int +smb_ctx_setpwhash(smb_ctx_t *ctx, + const uchar_t *nthash, const uchar_t *lmhash) +{ + + /* Need ct_password to be non-null. */ + if (ctx->ct_password[0] == '\0') + strlcpy(ctx->ct_password, "$HASH", + sizeof (ctx->ct_password)); + + /* + * Compute LM hash, NT hash. + */ + memcpy(ctx->ct_nthash, nthash, NTLM_HASH_SZ); + + /* The LM hash is optional */ + if (lmhash) { + memcpy(ctx->ct_nthash, nthash, NTLM_HASH_SZ); + } + + return (0); +} + int smb_ctx_setshare(struct smb_ctx *ctx, const char *share, int stype) { - if (strlen(share) >= SMB_MAXSHARENAMELEN) { + if (strlen(share) >= SMBIOC_MAX_NAME) { smb_error(dgettext(TEXT_DOMAIN, "share name '%s' too long"), 0, share); return (ENAMETOOLONG); @@ -754,10 +829,9 @@ smb_ctx_setshare(struct smb_ctx *ctx, const char *share, int stype) 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; + + ctx->ct_shtype_req = stype; + return (0); } @@ -766,9 +840,9 @@ 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) + if (ctx->ct_srvaddr_s) + free(ctx->ct_srvaddr_s); + if ((ctx->ct_srvaddr_s = strdup(addr)) == NULL) return (ENOMEM); return (0); } @@ -784,7 +858,7 @@ smb_parse_owner(char *pair, uid_t *uid, gid_t *gid) cp = strchr(pair, ':'); if (cp) { *cp++ = '\0'; - if (*cp) { + if (*cp && gid) { if (getgrnam_r(cp, &gr, buf, sizeof (buf)) != NULL) { *gid = gr.gr_gid; } else @@ -824,11 +898,8 @@ smb_ctx_opt(struct smb_ctx *ctx, int opt, const char *arg) 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; - } + /* share connect rights - ignored */ + ctx->ct_flags |= SMBCF_SRIGHTS; break; case 'N': ctx->ct_flags |= SMBCF_NOPWD; @@ -836,61 +907,40 @@ smb_ctx_opt(struct smb_ctx *ctx, int opt, const char *arg) 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); - } + if (cp) + *cp = '\0'; + error = smb_parse_owner(cp, &ctx->ct_owner, NULL); free(p); break; case 'P': -/* ctx->ct_ssn.ioc_opt |= SMBCOPT_PERMANENT; */ +/* ctx->ct_vopt |= SMBCOPT_PERMANENT; */ break; case 'R': - ctx->ct_ssn.ioc_retrycount = atoi(arg); + /* retry count - ignored */ break; case 'T': - ctx->ct_ssn.ioc_timeout = atoi(arg); + /* timeout - ignored */ break; - case 'W': + case 'D': /* domain */ + case 'W': /* workgroup (legacy alias) */ nls_str_upper(tmp, arg); - error = smb_ctx_setworkgroup(ctx, tmp, TRUE); + error = smb_ctx_setdomain(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 - +/* + * Original code injected iconv tables into the kernel. + * Not sure if we'll need this or not... REVISIT + */ +#ifdef KICONV_SUPPORT static int smb_addiconvtbl(const char *to, const char *from, const uchar_t *tbl) { - int error; + int error = 0; - /* - * 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, @@ -898,47 +948,44 @@ smb_addiconvtbl(const char *to, const char *from, const uchar_t *tbl) error, from, to); return (error); } -#endif - return (0); + return (error); } +#endif /* KICONV_SUPPORT */ /* - * Verify context before connect operation(s), + * Verify context info. before connect operation(s), * lookup specified server and try to fill all forgotten fields. + * Legacy name used by commands. */ int smb_ctx_resolve(struct smb_ctx *ctx) { struct smbioc_ossn *ssn = &ctx->ct_ssn; - struct smbioc_oshare *sh = &ctx->ct_sh; - struct nb_name nn; - struct sockaddr *sap; - struct sockaddr_nb *salocal, *saserver; - char *cp; + int error = 0; +#ifdef KICONV_SUPPORT uchar_t cstbl[256]; uint_t i; - int error = 0; - int browseok = ctx->ct_flags & SMBCF_BROWSEOK; - int renego = 0; +#endif ctx->ct_flags &= ~SMBCF_RESOLVED; - if (isatty(STDIN_FILENO)) - browseok = 0; - if (ctx->ct_fullserver == NULL || ctx->ct_fullserver[0] == 0) { + + if (ctx->ct_fullserver == NULL) { smb_error(dgettext(TEXT_DOMAIN, "no server name specified"), 0); return (EINVAL); } - if (ctx->ct_minlevel >= SMBL_SHARE && sh->ioc_share[0] == 0 && - !browseok) { + + if (ctx->ct_minlevel >= SMBL_SHARE && + ctx->ct_origshare == NULL) { smb_error(dgettext(TEXT_DOMAIN, "no share name specified for %s@%s"), - 0, ssn->ioc_user, ssn->ioc_srvname); + 0, ssn->ssn_user, ctx->ct_fullserver); return (EINVAL); } error = nb_ctx_resolve(ctx->ct_nb); if (error) return (error); +#ifdef KICONV_SUPPORT if (ssn->ioc_localcs[0] == 0) strcpy(ssn->ioc_localcs, "default"); /* XXX: locale name ? */ error = smb_addiconvtbl("tolower", ssn->ioc_localcs, nls_lower); @@ -963,177 +1010,57 @@ smb_ctx_resolve(struct smb_ctx *ctx) 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); - } +#endif /* KICONV_SUPPORT */ /* - * 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. + * Lookup the IP address. + * Puts a list in ct_addrinfo */ - 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). - */ - } + error = smb_ctx_getaddr(ctx); if (error) { smb_error(dgettext(TEXT_DOMAIN, "can't get server address"), error); return (error); } + assert(ctx->ct_addrinfo != NULL); - /* 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_findvc(ctx, SMBL_VC, 0); - if (error == 0) { - /* re-use and existing VC */ - ctx->ct_flags |= SMBCF_RESOLVED | SMBCF_SSNACTIVE; - return (0); - } - - /* Make a new connection via smb_ctx_negotiate()... */ - 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); + /* + * If we have a user name but no password, + * check for a keychain entry. + * XXX: Only for auth NTLM? + */ + if (ctx->ct_user[0] == '\0') { /* - * A share was selected, authenticate button was pressed, - * or anon-authentication failed getting browse list. + * No user name (anonymous session). + * The minauth checks do not apply. */ - } - if ((ctx->ct_secblob == NULL) && (ctx->ct_flags & SMBCF_AUTHREQ || - (ssn->ioc_password[0] == '\0' && - !(ctx->ct_flags & SMBCF_NOPWD)))) { -reauth: + ctx->ct_authflags = SMB_AT_ANON; + } else { /* - * 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. + * Have a user name. + * If we don't have a p/w yet, + * try the keychain. */ - 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); + if (ctx->ct_password[0] == '\0') + (void) smb_get_keychain(ctx); /* - * Re-authenticate button was pressed? + * If we're doing p/w based auth, + * that means not using Kerberos. */ - 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); - } + if (ctx->ct_password[0] != '\0') + ctx->ct_authflags &= ~SMB_AT_KRB5; + /* + * Mask out disallowed auth types. + */ + ctx->ct_authflags &= ctx->ct_minauth; + } + if (ctx->ct_authflags == 0) { + smb_error(dgettext(TEXT_DOMAIN, + "no valid auth. types"), 0); + return (ENOTSUP); } - ctx->ct_flags |= SMBCF_RESOLVED; + ctx->ct_flags |= SMBCF_RESOLVED; if (smb_debug) dump_ctx("after smb_ctx_resolve", ctx); @@ -1143,50 +1070,23 @@ reauth: int smb_open_driver() { - char buf[20]; - int err, fd, i; + int err, fd; 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); + if (fd < 0) { + err = errno; + smb_error(dgettext(TEXT_DOMAIN, + "failed to open driver"), err); + return (-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 (ioctl(fd, SMBIOC_GETVERS, &version) < 0) + version = 0; if (version != NSMB_VERSION) { smb_error(dgettext(TEXT_DOMAIN, "incorrect driver version"), 0); @@ -1194,18 +1094,21 @@ opened: return (-1); } + /* This handle controls per-process resources. */ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + return (fd); } -static int +int smb_ctx_gethandle(struct smb_ctx *ctx) { - int err, fd; + int fd; - if (ctx->ct_fd != -1) { + if (ctx->ct_dev_fd != -1) { rpc_cleanup_smbctx(ctx); - close(ctx->ct_fd); - ctx->ct_fd = -1; + close(ctx->ct_dev_fd); + ctx->ct_dev_fd = -1; ctx->ct_flags &= ~SMBCF_SSNACTIVE; } @@ -1213,733 +1116,195 @@ smb_ctx_gethandle(struct smb_ctx *ctx) if (fd < 0) return (ENODEV); - ctx->ct_fd = fd; + ctx->ct_dev_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; */ - - 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: - return (rc); -} - -int -smb_ctx_findvc(struct smb_ctx *ctx, int level, int flags) -{ - struct smbioc_lookup rq; - int error = 0; - - 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; - - return (smb_ctx_ioctl(ctx, SMBIOC_FINDVC, &rq)); -} /* - * adds a GSSAPI wrapper + * Find or create a connection + logon session */ -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) +int +smb_ctx_get_ssn(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; + int err = 0; - /* - * 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 -}; + if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) + return (EINVAL); -/* - * 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; + if (ctx->ct_dev_fd < 0) { + if ((err = smb_ctx_gethandle(ctx))) + return (err); } - 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. + * Check whether the driver already has a VC + * we can use. If so, we're done! */ - 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"; + err = smb_ctx_findvc(ctx); + if (err == 0) { + DPRINT("found an existing VC"); + } else { /* - * Avoid logging the typical "No credentials cache found" + * This calls the IOD to create a new session. */ - 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); -} + DPRINT("setup a new VC"); + err = smb_ctx_newvc(ctx); + if (err != 0) + return (err); -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"; + /* + * Call findvc again. The new VC sould be + * found in the driver this time. + */ + err = smb_ctx_findvc(ctx); } - 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"); + return (err); } -#endif - /* - * We navigate the SPNEGO & ASN1 encoding to find a kerberos principal - * Note: driver no longer puts length at start of blob. + * Get the string representation of a share "use" type, + * as needed for the "service" in tree connect. */ -char * -smb_ctx_blob2principal( - struct smb_ctx *ctx, - smbioc_ossn_t *ssn, - char **prinp) +static const char * +smb_use_type_str(smb_use_shtype_t stype) { - 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 + const char *pp; - /* 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"; + switch (stype) { + default: + case USE_WILDCARD: + pp = "?????"; + break; + case USE_DISKDEV: + pp = "A:"; + break; + case USE_SPOOLDEV: + pp = "LPT1:"; + break; + case USE_CHARDEV: + pp = "COMM"; + break; + case USE_IPC: + pp = "IPC"; + break; } - return (failure); + return (pp); } - +/* + * Find or create a tree connection + */ int -smb_ctx_negotiate(struct smb_ctx *ctx, int level, int flags, char *workgroup) +smb_ctx_get_tree(struct smb_ctx *ctx) { - struct smbioc_lookup rq; - int error = 0; - char *failure = NULL; - char *principal = NULL; - char c; - int i; - ssize_t *outtoklen; - uchar_t *blob; + smbioc_tcon_t *tcon = NULL; + const char *stype; + int cmd, err = 0; - /* - * 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); + if (ctx->ct_dev_fd < 0 || + ctx->ct_origshare == NULL) { 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; + cmd = SMBIOC_TREE_CONNECT; + tcon = malloc(sizeof (*tcon)); + if (tcon == NULL) + return (ENOMEM); + bzero(tcon, sizeof (*tcon)); + tcon->tc_flags = SMBLK_CREATE; + tcon->tc_opt = 0; - /* 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; + /* The share name */ + strlcpy(tcon->tc_sh.sh_name, ctx->ct_origshare, + sizeof (tcon->tc_sh.sh_name)); -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! */ + /* The share "use" type. */ + stype = smb_use_type_str(ctx->ct_shtype_req); + strlcpy(tcon->tc_sh.sh_type_req, stype, + sizeof (tcon->tc_sh.sh_type_req)); /* - * Negotiate failed with "extended security". + * Todo: share passwords for share-level 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 + * The driver does the actual TCON call. */ - if (smb_verbose) { - smb_error(dgettext(TEXT_DOMAIN, - "%s (extended security negotiate)"), error, failure); + if (ioctl(ctx->ct_dev_fd, cmd, tcon) == -1) { + err = errno; + goto out; } /* - * 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. + * Check the returned share type */ - 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); + DPRINT("ret. sh_type: \"%s\"", tcon->tc_sh.sh_type_ret); + if (ctx->ct_shtype_req != USE_WILDCARD && + 0 != strcmp(stype, tcon->tc_sh.sh_type_ret)) { + smb_error(dgettext(TEXT_DOMAIN, + "%s: incompatible share type"), + 0, ctx->ct_origshare); + err = EINVAL; } - /* - * 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. - */ +out: + if (tcon != NULL) + free(tcon); - return (error); + return (err); } - +/* + * Return the hflags2 word for an smb_ctx. + */ int -smb_ctx_tdis(struct smb_ctx *ctx) +smb_ctx_flags2(struct smb_ctx *ctx) { - struct smbioc_lookup rq; /* XXX may be used, someday */ - int error = 0; + uint16_t flags2; - if (ctx->ct_fd < 0) { + if (ioctl(ctx->ct_dev_fd, SMBIOC_FLAGS2, &flags2) == -1) { 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); + "can't get flags2 for a session"), errno); + return (-1); } - return (error); + return (flags2); } - +/* + * Get the transport level session key. + * Must already have an active SMB session. + */ int -smb_ctx_lookup(struct smb_ctx *ctx, int level, int flags) +smb_ctx_get_ssnkey(struct smb_ctx *ctx, uchar_t *key, size_t len) { - 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); + if (len < SMBIOC_HASH_SZ) 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 - */ - 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"); - } + if (ioctl(ctx->ct_dev_fd, SMBIOC_GETSSNKEY, key) == -1) + return (errno); -out: - if (failure) { - error = errno; - smb_error(dgettext(TEXT_DOMAIN, - "%s phase failed"), error, failure); - } - return (error); + return (0); } + /* - * Return the hflags2 word for an smb_ctx. + * RC file parsing stuff */ -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); - } - return (flags2); -} +struct nv { + char *name; + int value; +} minauth_table[] = { + /* Allowed auth. types */ + { "kerberos", SMB_AT_KRB5 }, + { "ntlmv2", SMB_AT_KRB5|SMB_AT_NTLM2 }, + { "ntlm", SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1 }, + { "lm", SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1 }, + { "none", SMB_AT_KRB5|SMB_AT_NTLM2|SMB_AT_NTLM1|SMB_AT_LM1| + SMB_AT_ANON }, + { NULL } +}; + /* * level values: @@ -1954,7 +1319,7 @@ smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) char *p; int error; -#ifdef NOT_DEFINED +#ifdef KICONV_SUPPORT if (level > 0) { rc_getstringptr(smb_rc, sname, "charsets", &p); if (p) { @@ -1970,56 +1335,19 @@ smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) 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 { + struct nv *nvp; + for (nvp = minauth_table; nvp->name; nvp++) + if (strcmp(p, nvp->name) == 0) + break; + if (nvp->name) + ctx->ct_minauth = nvp->value; + else { /* * Unknown minimum authentication level. */ @@ -2036,15 +1364,15 @@ smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) * "signing" was set in this section; override * the current signing settings. */ - ctx->ct_ssn.ioc_opt &= ~SMBVOPT_SIGNING_MASK; + ctx->ct_vopt &= ~SMBVOPT_SIGNING_MASK; if (strcmp(p, "disabled") == 0) { /* leave flags zero (expr for lint) */ - (void) ctx->ct_ssn.ioc_opt; + (void) ctx->ct_vopt; } else if (strcmp(p, "enabled") == 0) { - ctx->ct_ssn.ioc_opt |= + ctx->ct_vopt |= SMBVOPT_SIGNING_ENABLED; } else if (strcmp(p, "required") == 0) { - ctx->ct_ssn.ioc_opt |= + ctx->ct_vopt |= SMBVOPT_SIGNING_ENABLED | SMBVOPT_SIGNING_REQUIRED; } else { @@ -2068,7 +1396,7 @@ smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) rc_getstringptr(smb_rc, sname, "workgroup", &p); if (p) { nls_str_upper(p, p); - error = smb_ctx_setworkgroup(ctx, p, 0); + error = smb_ctx_setdomain(ctx, p, 0); if (error) smb_error(dgettext(TEXT_DOMAIN, "workgroup specification in the " @@ -2077,7 +1405,7 @@ smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) rc_getstringptr(smb_rc, sname, "domain", &p); if (p) { nls_str_upper(p, p); - error = smb_ctx_setworkgroup(ctx, p, 0); + error = smb_ctx_setdomain(ctx, p, 0); if (error) smb_error(dgettext(TEXT_DOMAIN, "domain specification in the " @@ -2132,11 +1460,25 @@ smb_ctx_readrcsection(struct smb_ctx *ctx, const char *sname, int level) int smb_ctx_readrc(struct smb_ctx *ctx) { - char sname[SMB_MAXSRVNAMELEN + SMB_MAXUSERNAMELEN + - SMB_MAXSHARENAMELEN + 4]; + char *home; + char *sname = NULL; + int sname_max; + int err = 0; + + if ((home = getenv("HOME")) == NULL) + home = ctx->ct_home; + if ((err = smb_open_rcfile(home)) != 0) { + DPRINT("smb_open_rcfile, err=%d", err); + /* ignore any error here */ + return (0); + } - if (smb_open_rcfile(ctx) != 0) + sname_max = 3 * SMBIOC_MAX_NAME + 4; + sname = malloc(sname_max); + if (sname == NULL) { + err = ENOMEM; goto done; + } /* * default parameters (level=0) @@ -2148,47 +1490,51 @@ smb_ctx_readrc(struct smb_ctx *ctx) * 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) + if (ctx->ct_fullserver == NULL) goto done; - /* * SERVER parameters. */ - smb_ctx_readrcsection(ctx, ctx->ct_ssn.ioc_srvname, 1); + smb_ctx_readrcsection(ctx, ctx->ct_fullserver, 1); /* * If we don't have a user name, we can't read any of the * [server:user...] sections. */ - if (ctx->ct_ssn.ioc_user[0] == 0) + if (ctx->ct_user[0] == 0) goto done; - /* * SERVER:USER parameters */ - snprintf(sname, sizeof (sname), "%s:%s", - ctx->ct_ssn.ioc_srvname, - ctx->ct_ssn.ioc_user); + snprintf(sname, sname_max, "%s:%s", + ctx->ct_fullserver, + ctx->ct_user); smb_ctx_readrcsection(ctx, sname, 2); + /* * If we don't have a share name, we can't read any of the * [server:user:share] sections. */ - if (ctx->ct_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); - } + if (ctx->ct_origshare == NULL) + goto done; + /* + * SERVER:USER:SHARE parameters + */ + snprintf(sname, sname_max, "%s:%s:%s", + ctx->ct_fullserver, + ctx->ct_user, + ctx->ct_origshare); + smb_ctx_readrcsection(ctx, sname, 3); done: + if (sname) + free(sname); + smb_close_rcfile(); if (smb_debug) dump_ctx("after smb_ctx_readrc", ctx); + if (err) + DPRINT("err=%d\n", err); - return (0); + return (err); } diff --git a/usr/src/lib/libsmbfs/smb/derparse.c b/usr/src/lib/libsmbfs/smb/derparse.c index cc7d61c6bd..0180e064ed 100644 --- a/usr/src/lib/libsmbfs/smb/derparse.c +++ b/usr/src/lib/libsmbfs/smb/derparse.c @@ -1,4 +1,3 @@ -/* // Copyright (C) 2002 Microsoft Corporation // All rights reserved. // @@ -23,10 +22,6 @@ // ///////////////////////////////////////////////////////////// -*/ - -#pragma ident "%Z%%M% %I% %E% SMI" - #include <stdlib.h> #include <stdio.h> #include <memory.h> @@ -34,12 +29,11 @@ #include "spnego.h" #include "derparse.h" -/* // // The GSS Mechanism OID enumeration values (SPNEGO_MECH_OID) control which offset in // the array below, that a mechanism can be found. // -*/ + #pragma error_messages (off,E_INITIALIZATION_TYPE_MISMATCH) MECH_OID g_stcMechOIDList [] = { @@ -55,7 +49,6 @@ MECH_OID g_stcMechOIDList [] = }; #pragma error_messages (default,E_INITIALIZATION_TYPE_MISMATCH) -/* ///////////////////////////////////////////////////////////////////////////// // // Function: @@ -78,7 +71,6 @@ MECH_OID g_stcMechOIDList [] = // process lengths that take more than 4 bytes. // //////////////////////////////////////////////////////////////////////////// -*/ int ASNDerGetLength( unsigned char* pbLengthData, long nBoundaryLength, long* pnLength, long* pnNumLengthBytes ) @@ -180,7 +172,6 @@ int ASNDerGetLength( unsigned char* pbLengthData, long nBoundaryLength, long* pn } -/* ///////////////////////////////////////////////////////////////////////////// // // Function: @@ -206,7 +197,6 @@ int ASNDerGetLength( unsigned char* pbLengthData, long nBoundaryLength, long* pn // length must also not exceed the specified boundary length . // //////////////////////////////////////////////////////////////////////////// -*/ int ASNDerCheckToken( unsigned char* pbTokenData, unsigned char nToken, long nLengthWithToken, long nBoundaryLength, @@ -271,7 +261,6 @@ int ASNDerCheckToken( unsigned char* pbTokenData, unsigned char nToken, return nReturn; } -/* ///////////////////////////////////////////////////////////////////////////// // // Function: @@ -292,7 +281,6 @@ int ASNDerCheckToken( unsigned char* pbTokenData, unsigned char nToken, // Checks the data pointed to by pbTokenData for the specified OID. // //////////////////////////////////////////////////////////////////////////// -*/ int ASNDerCheckOID( unsigned char* pbTokenData, SPNEGO_MECH_OID nMechOID, long nBoundaryLength, long* pnTokenLength ) @@ -327,7 +315,6 @@ int ASNDerCheckOID( unsigned char* pbTokenData, SPNEGO_MECH_OID nMechOID, long n return nReturn; } -/* ///////////////////////////////////////////////////////////////////////////// // // Function: @@ -345,7 +332,6 @@ int ASNDerCheckOID( unsigned char* pbTokenData, SPNEGO_MECH_OID nMechOID, long n // enough to describea length. // //////////////////////////////////////////////////////////////////////////// -*/ int ASNDerCalcNumLengthBytes( long nLength ) { diff --git a/usr/src/lib/libsmbfs/smb/file.c b/usr/src/lib/libsmbfs/smb/file.c index e7a02e77df..74630fdd91 100644 --- a/usr/src/lib/libsmbfs/smb/file.c +++ b/usr/src/lib/libsmbfs/smb/file.c @@ -33,7 +33,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -56,35 +56,32 @@ #include <sys/types.h> #include <sys/file.h> +#include <netsmb/smb.h> #include <netsmb/smb_lib.h> -#include <cflib.h> -#include "charsets.h" #include "private.h" int -smb_fh_close(struct smb_ctx *ctx, smbfh fh) +smb_fh_close(struct smb_ctx *ctx, int fh) { struct smb_rq *rqp; struct mbdata *mbp; - int serr; + int error; - serr = smb_rq_init(ctx, SMB_COM_CLOSE, 0, &rqp); - if (serr != 0) - return (serr); + error = smb_rq_init(ctx, SMB_COM_CLOSE, &rqp); + if (error != 0) + return (error); mbp = smb_rq_getrequest(rqp); - mb_put_uint16le(mbp, fh); + smb_rq_wstart(rqp); + mb_put_uint16le(mbp, (uint16_t)fh); mb_put_uint32le(mbp, 0); /* time stamp */ smb_rq_wend(rqp); - serr = smb_rq_simple(rqp); - if (serr != 0) { - smb_rq_done(rqp); - return (serr); - } - mbp = smb_rq_getreply(rqp); + mb_put_uint16le(mbp, 0); /* byte count */ + + error = smb_rq_simple(rqp); smb_rq_done(rqp); - return (serr); + return (error); } int @@ -93,45 +90,32 @@ smb_fh_ntcreate( int flags, int req_acc, int efattr, int share_acc, int open_disp, int create_opts, int impersonation, - smbfh *fhp, uint32_t *action_taken) + int *fhp, uint32_t *action_taken) { struct smb_rq *rqp; struct mbdata *mbp; + char *pathsizep; + int pathstart, pathsize; + int error, flags2, uc; + uint16_t fh; uint8_t wc; - size_t pathlen, pathsize; - int error, flags2; - uint16_t *upath = NULL; flags2 = smb_ctx_flags2(ctx); if (flags2 == -1) return (EIO); + uc = flags2 & SMB_FLAGS2_UNICODE; - error = smb_rq_init(ctx, SMB_COM_NT_CREATE_ANDX, 42, &rqp); + error = smb_rq_init(ctx, SMB_COM_NT_CREATE_ANDX, &rqp); if (error != 0) return (error); - if (flags2 & SMB_FLAGS2_UNICODE) { - upath = convert_utf8_to_leunicode(path); - if (upath == NULL) { - smb_error(dgettext(TEXT_DOMAIN, - "%s: failed converting to UCS-2"), 0, path); - error = EINVAL; - goto out; - } - pathlen = unicode_strlen(upath); - pathsize = (pathlen + 1) * 2; - } else { - pathlen = strlen(path); - pathsize = pathlen + 1; - } - mbp = smb_rq_getrequest(rqp); - mb_put_uint8(mbp, 0xff); /* secondary command */ - mb_put_uint8(mbp, 0); /* MBZ */ + smb_rq_wstart(rqp); + mb_put_uint16le(mbp, 0xff); /* secondary command */ mb_put_uint16le(mbp, 0); /* offset to next command (none) */ - mb_put_uint8(mbp, 0); /* MBZ */ - mb_put_uint16le(mbp, pathsize); /* path size (bytes) */ - mb_put_uint32le(mbp, 0); /* create flags (oplock) */ + mb_put_uint8(mbp, 0); /* MBZ (pad?) */ + mb_fit(mbp, 2, &pathsizep); /* path size - fill in below */ + mb_put_uint32le(mbp, flags); /* create flags (oplock) */ mb_put_uint32le(mbp, 0); /* FID - basis for path if not root */ mb_put_uint32le(mbp, req_acc); mb_put_uint64le(mbp, 0); /* initial alloc. size */ @@ -139,16 +123,28 @@ smb_fh_ntcreate( mb_put_uint32le(mbp, share_acc); /* share access mode */ mb_put_uint32le(mbp, open_disp); /* open disposition */ mb_put_uint32le(mbp, create_opts); /* create_options */ - mb_put_uint32le(mbp, NTCREATEX_IMPERSONATION_IMPERSONATION); /* (?) */ + mb_put_uint32le(mbp, impersonation); mb_put_uint8(mbp, 0); /* security flags (?) */ smb_rq_wend(rqp); + smb_rq_bstart(rqp); + if (uc) { + /* + * We're about to put a unicode string. We know + * we're misaligned at this point, and need to + * save the mb_count at the start of the string, + * not at the alignment padding placed before it. + * So add the algnment padding by hand here. + */ + mb_put_uint8(mbp, 0); + } + pathstart = mbp->mb_count; + mb_put_dstring(mbp, path, uc); + smb_rq_bend(rqp); - /* XXX: Need a "put string" function. */ - if (flags2 & SMB_FLAGS2_UNICODE) { - mb_put_uint8(mbp, 0); /* pad byte - align(2) for Unicode */ - mb_put_mem(mbp, (char *)upath, pathsize); - } else - mb_put_mem(mbp, path, pathsize); + /* Now go back and fill in pathsizep */ + pathsize = mbp->mb_count - pathstart; + pathsizep[0] = pathsize & 0xFF; + pathsizep[1] = (pathsize >> 8); error = smb_rq_simple(rqp); if (error) @@ -159,8 +155,8 @@ smb_fh_ntcreate( * spec says 26 for word count, but 34 words are defined * and observed from win2000 */ - wc = rqp->rq_wcount; - if (wc < 26) { + error = mb_get_uint8(mbp, &wc); + if (error || wc < 26) { smb_error(dgettext(TEXT_DOMAIN, "%s: open failed, bad word count"), 0, path); error = EBADRPC; @@ -170,7 +166,7 @@ smb_fh_ntcreate( mb_get_uint8(mbp, NULL); /* mbz */ mb_get_uint16le(mbp, NULL); /* andxoffset */ mb_get_uint8(mbp, NULL); /* oplock lvl granted */ - mb_get_uint16le(mbp, fhp); /* FID */ + mb_get_uint16le(mbp, &fh); /* FID */ mb_get_uint32le(mbp, action_taken); #if 0 /* skip decoding the rest */ mb_get_uint64le(mbp, NULL); /* creation time */ @@ -183,14 +179,16 @@ smb_fh_ntcreate( mb_get_uint16le(mbp, NULL); /* file type */ mb_get_uint16le(mbp, NULL); /* device state */ mb_get_uint8(mbp, NULL); /* directory (boolean) */ -#endif /* skip decoding */ +#endif + + /* success! */ + *fhp = fh; + error = 0; out: - if (upath) - free(upath); smb_rq_done(rqp); - return (0); + return (error); } /* @@ -198,7 +196,7 @@ out: * Converts Unix-style open call to NTCreate. */ int -smb_fh_open(struct smb_ctx *ctx, const char *path, int oflag, smbfh *fhp) +smb_fh_open(struct smb_ctx *ctx, const char *path, int oflag, int *fhp) { int error, mode, open_disp, req_acc, share_acc; char *p, *ntpath = NULL; @@ -273,7 +271,7 @@ smb_fh_open(struct smb_ctx *ctx, const char *path, int oflag, smbfh *fhp) } int -smb_fh_read(struct smb_ctx *ctx, smbfh fh, off_t offset, size_t count, +smb_fh_read(struct smb_ctx *ctx, int fh, off_t offset, size_t count, char *dst) { struct smbioc_rw rwrq; @@ -283,14 +281,14 @@ smb_fh_read(struct smb_ctx *ctx, smbfh fh, off_t offset, size_t count, rwrq.ioc_base = dst; rwrq.ioc_cnt = count; rwrq.ioc_offset = offset; - if (ioctl(ctx->ct_fd, SMBIOC_READ, &rwrq) == -1) { + if (ioctl(ctx->ct_dev_fd, SMBIOC_READ, &rwrq) == -1) { return (-1); } return (rwrq.ioc_cnt); } int -smb_fh_write(struct smb_ctx *ctx, smbfh fh, off_t offset, size_t count, +smb_fh_write(struct smb_ctx *ctx, int fh, off_t offset, size_t count, const char *src) { struct smbioc_rw rwrq; @@ -300,7 +298,7 @@ smb_fh_write(struct smb_ctx *ctx, smbfh fh, off_t offset, size_t count, rwrq.ioc_base = (char *)src; rwrq.ioc_cnt = count; rwrq.ioc_offset = offset; - if (ioctl(ctx->ct_fd, SMBIOC_WRITE, &rwrq) == -1) { + if (ioctl(ctx->ct_dev_fd, SMBIOC_WRITE, &rwrq) == -1) { return (-1); } return (rwrq.ioc_cnt); @@ -315,7 +313,7 @@ smb_fh_write(struct smb_ctx *ctx, smbfh fh, off_t offset, size_t count, * and on output *rdlen is the received length. */ int -smb_fh_xactnp(struct smb_ctx *ctx, smbfh fh, +smb_fh_xactnp(struct smb_ctx *ctx, int fh, int tdlen, const char *tdata, /* transmit */ int *rdlen, char *rdata, /* receive */ int *more) diff --git a/usr/src/lib/libsmbfs/smb/findvc.c b/usr/src/lib/libsmbfs/smb/findvc.c new file mode 100644 index 0000000000..cfe2cad9e7 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/findvc.c @@ -0,0 +1,139 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Find existing an VC given a list of addresses. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +/* + * Ask the driver if it has a VC with this IP address. + */ +static int +findvc(struct smb_ctx *ctx, struct addrinfo *ai) +{ + smbioc_ossn_t *ssn = &ctx->ct_ssn; + + /* + * Copy the passed address into ssn_srvaddr, + * but first sanity-check lengths. Also, + * zero it first to avoid trailing junk. + */ + if (ai->ai_addrlen > sizeof (ssn->ssn_srvaddr)) + return (EINVAL); + bzero(&ssn->ssn_srvaddr, sizeof (ssn->ssn_srvaddr)); + bcopy(ai->ai_addr, &ssn->ssn_srvaddr, ai->ai_addrlen); + + if (ioctl(ctx->ct_dev_fd, SMBIOC_SSN_FIND, ssn) == -1) + return (errno); + + return (0); +} + +/* + * Find (and reuse) an existing VC. + * See also: newvc.c + */ +int +smb_ctx_findvc(struct smb_ctx *ctx) +{ + struct addrinfo *ai; + int err; + + /* Should already have the address list. */ + if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) + return (EINVAL); + + for (ai = ctx->ct_addrinfo; ai; ai = ai->ai_next) { + + switch (ai->ai_family) { + + case AF_INET: + case AF_INET6: + case AF_NETBIOS: + err = findvc(ctx, ai); + break; + + default: + DPRINT("skipped family %d", ai->ai_family); + err = EPROTONOSUPPORT; + break; + } + + if (err == 0) { + /* re-use an existing VC */ + ctx->ct_flags |= SMBCF_SSNACTIVE; + return (0); + } + } + + return (ENOENT); +} + +/* + * Forcibly disconnect the current session, even if + * there are others using it! This is used by the + * SMB server netlogon when it wants to setup a new + * logon session and does not want any re-use. + */ +int +smb_ctx_kill(struct smb_ctx *ctx) +{ + + if (ioctl(ctx->ct_dev_fd, SMBIOC_SSN_KILL, NULL) == -1) + return (errno); + + return (0); +} diff --git a/usr/src/lib/libsmbfs/smb/getaddr.c b/usr/src/lib/libsmbfs/smb/getaddr.c new file mode 100644 index 0000000000..2847d858cb --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/getaddr.c @@ -0,0 +1,215 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Functions to get list of addresses (TCP and/or NetBIOS) + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +void +dump_addrinfo(struct addrinfo *ai) +{ + int i; + + if (ai == NULL) { + printf("ai==NULL\n"); + return; + } + + for (i = 0; ai; i++, ai = ai->ai_next) { + printf("ai[%d]: af=%d, len=%d", i, + ai->ai_family, ai->ai_addrlen); + dump_sockaddr(ai->ai_addr); + if (ai->ai_canonname) { + printf("ai[%d]: cname=\"%s\"\n", + i, ai->ai_canonname); + } + } +} + +void +dump_sockaddr(struct sockaddr *sa) +{ + char paddrbuf[INET6_ADDRSTRLEN]; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + int af = sa->sa_family; + const char *ip; + + printf(" saf=%d,", af); + switch (af) { + case AF_NETBIOS: /* see nbns_rq.c */ + case AF_INET: + sin = (void *)sa; + ip = inet_ntop(AF_INET, &sin->sin_addr, + paddrbuf, sizeof (paddrbuf)); + break; + case AF_INET6: + sin6 = (void *)sa; + ip = inet_ntop(AF_INET6, &sin6->sin6_addr, + paddrbuf, sizeof (paddrbuf)); + break; + default: + ip = "?"; + break; + } + printf(" IP=%s\n", ip); +} + + +/* + * SMB client name resolution - normal, and/or NetBIOS. + * Returns an EAI_xxx error number like getaddrinfo(3) + */ +int +smb_ctx_getaddr(struct smb_ctx *ctx) +{ + struct nb_ctx *nbc = ctx->ct_nb; + struct addrinfo hints, *res; + char *srvaddr_str; + int gaierr, gaierr2; + + if (ctx->ct_fullserver == NULL || ctx->ct_fullserver[0] == '\0') + return (EAI_NONAME); + + if (ctx->ct_addrinfo != NULL) { + freeaddrinfo(ctx->ct_addrinfo); + ctx->ct_addrinfo = NULL; + } + + /* + * If the user specified an address, use it, + * and don't do NetBIOS lookup. + */ + if (ctx->ct_srvaddr_s) { + srvaddr_str = ctx->ct_srvaddr_s; + nbc->nb_flags &= ~NBCF_NS_ENABLE; + } else + srvaddr_str = ctx->ct_fullserver; + + /* + * Default the server name we'll use in the + * protocol (i.e. NTLM, tree connect). + * If we get a canonical name, we'll + * overwrite this below. + */ + strlcpy(ctx->ct_srvname, ctx->ct_fullserver, + sizeof (ctx->ct_srvname)); + + /* + * Try to lookup the host address using the + * normal name-to-IP address mechanisms. + * If that fails, we MAY try NetBIOS. + */ + memset(&hints, 0, sizeof (hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + gaierr = getaddrinfo(srvaddr_str, NULL, &hints, &res); + if (gaierr == 0) { +#if 1 + /* + * XXX Temporarily work-around CR 6831339: + * getaddrinfo() sets ai_canonname incorrectly + */ + char tmphost[256]; + gaierr2 = getnameinfo(res->ai_addr, res->ai_addrlen, + tmphost, sizeof (tmphost), + NULL, 0, NI_NAMEREQD); + if (gaierr2 == 0) { + DPRINT("cname: %s", tmphost); + strlcpy(ctx->ct_srvname, tmphost, + sizeof (ctx->ct_srvname)); + } +#else + if (res->ai_canonname) + strlcpy(ctx->ct_srvname, res->ai_canonname, + sizeof (ctx->ct_srvname)); +#endif + ctx->ct_addrinfo = res; + return (0); + } + + /* + * If regular IP name lookup failed, try NetBIOS, + * but only if given a valid NetBIOS name and if + * NetBIOS name lookup is enabled. + * + * Note: we only have ssn_srvname if the full name + * was also a valid NetBIOS name. + */ + if (nbc->nb_flags & NBCF_NS_ENABLE) { + gaierr2 = nbns_getaddrinfo(ctx->ct_fullserver, nbc, &res); + if (gaierr2 == 0) { + if (res->ai_canonname) + strlcpy(ctx->ct_srvname, + res->ai_canonname, + sizeof (ctx->ct_srvname)); + ctx->ct_addrinfo = res; + return (0); + } + } + + /* + * Return the original error from getaddrinfo + */ + if (smb_verbose) { + smb_error(dgettext(TEXT_DOMAIN, + "getaddrinfo: %s: %s"), 0, + ctx->ct_fullserver, + gai_strerror(gaierr)); + } + return (gaierr); +} diff --git a/usr/src/lib/libsmbfs/smb/iod_cl.c b/usr/src/lib/libsmbfs/smb/iod_cl.c new file mode 100644 index 0000000000..b28ffdb39a --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/iod_cl.c @@ -0,0 +1,195 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Client-side interface to the IO Daemon (IOD) + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <libintl.h> +#include <door.h> + +#include <sys/byteorder.h> +#include <sys/types.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +static const char smbiod_path[] = "/usr/lib/smbfs/smbiod"; + +/* + * This is constant for the life of a process, + * and initialized at startup, so no locks. + */ +static char door_path[40]; + +char * +smb_iod_door_path(void) +{ + static const char fmtR[] = "/var/run/smbiod-%d"; + static const char fmtU[] = "/tmp/.smbiod-%d"; + const char *fmt; + uid_t uid; + + if (door_path[0] == '\0') { + uid = getuid(); + fmt = (uid == 0) ? fmtR : fmtU; + snprintf(door_path, sizeof (door_path), fmt, uid); + } + + return (door_path); +} + +/* + * Open the door (client side) and + * find out if the service is there. + */ +int +smb_iod_open_door(int *fdp) +{ + door_arg_t da; + char *path; + int fd, rc; + int err = 0; + + path = smb_iod_door_path(); + fd = open(path, O_RDONLY, 0); + if (fd < 0) + return (errno); + + /* + * Make sure the IOD is running. + * Pass NULL args. + */ + memset(&da, 0, sizeof (da)); + da.rbuf = (void *) &err; + da.rsize = sizeof (err); + rc = door_call(fd, &da); + if (rc < 0) { + err = errno; + close(fd); + return (err); + } + if (err != 0) { + close(fd); + return (err); + } + + /* This handle controls per-process resources. */ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + + *fdp = fd; + return (0); +} + +/* + * Start the IOD and wait until we can + * open its client-side door. + */ +static int +start_iod(int *fdp) +{ + int err, pid, t; + + pid = vfork(); + if (pid < 0) + return (errno); + + /* + * child: start smbiod + */ + if (pid == 0) { + char *argv[2]; + argv[0] = "smbiod"; + argv[1] = NULL; + execv(smbiod_path, argv); + return (errno); + } + + /* + * parent: wait for smbiod to start + */ + for (t = 0; t < 10; t++) { + sleep(1); + err = smb_iod_open_door(fdp); + if (err == 0) + break; + } + + return (err); +} + + +/* + * Start smbiod if necessary, and then + * ask it to connect using the info in ctx. + */ +int +smb_iod_cl_newvc(smb_ctx_t *ctx) +{ + door_arg_t da; + int fd, err = 0; + + err = smb_iod_open_door(&fd); + if (err != 0) { + err = start_iod(&fd); + if (err) + return (err); + } + + da.data_ptr = (void *) &ctx->ct_iod_ssn; + da.data_size = sizeof (ctx->ct_iod_ssn); + da.desc_ptr = NULL; + da.desc_num = 0; + da.rbuf = (void *) &err; + da.rsize = sizeof (err); + if (door_call(fd, &da) < 0) { + err = errno; + DPRINT("door_call, err=%d", err); + } + close(fd); + + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/iod_wk.c b/usr/src/lib/libsmbfs/smb/iod_wk.c new file mode 100644 index 0000000000..89f3eb3fa0 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/iod_wk.c @@ -0,0 +1,176 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Functions called by the IO deamon (IOD). + * Here in the library to simplify testing. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <libintl.h> + +#include <sys/byteorder.h> +#include <sys/types.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +/* + * Be the reader thread for this VC. + */ +int +smb_iod_work(smb_ctx_t *ctx) +{ + smbioc_ssn_work_t *work = &ctx->ct_work; + int vcst, err = 0; + + DPRINT("server: %s", ctx->ct_srvname); + + /* Calle should have opened these */ + if (ctx->ct_tran_fd == -1 || ctx->ct_dev_fd == -1) { + err = EINVAL; + goto out; + } + + /* + * This is the reader / reconnect loop. + * + * We could start with state "idle", but + * we know someone wants a connection to + * this server, so start in "vcactive". + * + * XXX: Add some syslog calls in here? + */ + vcst = SMBIOD_ST_VCACTIVE; + + for (;;) { + + switch (vcst) { + case SMBIOD_ST_IDLE: + /* + * Wait for driver requests to arrive + * for this VC, then return here. + * Next state is normally RECONNECT. + */ + DPRINT("state: idle"); + if (ioctl(ctx->ct_dev_fd, + SMBIOC_IOD_IDLE, &vcst) == -1) { + err = errno; + DPRINT("ioc_idle: err %d", err); + goto out; + } + continue; + + case SMBIOD_ST_RECONNECT: + DPRINT("state: reconnect"); + err = smb_iod_connect(ctx); + if (err == 0) { + vcst = SMBIOD_ST_VCACTIVE; + continue; + } + DPRINT("_iod_connect: err %d", err); + /* + * If the error was EAUTH, retry is + * not likely to succeed either, so + * just exit this thread. The user + * will need to run smbutil to get + * a new thread with new auth info. + */ + if (err == EAUTH) + goto out; + vcst = SMBIOD_ST_RCFAILED; + continue; + + case SMBIOD_ST_RCFAILED: + DPRINT("state: rcfailed"); + /* + * Reconnect failed. Kill off any + * requests waiting in the driver, + * then get ready to try again. + * Next state is normally IDLE. + */ + if (ioctl(ctx->ct_dev_fd, + SMBIOC_IOD_RCFAIL, &vcst) == -1) { + err = errno; + DPRINT("ioc_rcfail: err %d", err); + goto out; + } + continue; + + case SMBIOD_ST_VCACTIVE: + DPRINT("state: active"); + if (ioctl(ctx->ct_dev_fd, + SMBIOC_IOD_WORK, work) == -1) { + err = errno; + DPRINT("ioc_work: err %d", err); + goto out; + } + vcst = work->wk_out_state; + continue; + + case SMBIOD_ST_DEAD: + DPRINT("state: dead"); + err = 0; + goto out; + + default: + DPRINT("state: BAD(%d)", vcst); + err = EFAULT; + goto out; + } + } + +out: + if (ctx->ct_tran_fd != -1) { + close(ctx->ct_tran_fd); + ctx->ct_tran_fd = -1; + } + if (ctx->ct_dev_fd != -1) { + close(ctx->ct_dev_fd); + ctx->ct_dev_fd = -1; + } + + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/keychain.c b/usr/src/lib/libsmbfs/smb/keychain.c index 72a979022f..da19fd4d0b 100644 --- a/usr/src/lib/libsmbfs/smb/keychain.c +++ b/usr/src/lib/libsmbfs/smb/keychain.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * External interface to the libsmbfs/netsmb keychain * storage mechanism. This interface is consumed by @@ -37,15 +35,19 @@ #include <errno.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <unistd.h> #include <libintl.h> +#include <cflib.h> #include <netsmb/smb_dev.h> #include <netsmb/smb_lib.h> #include <netsmb/smb_keychain.h> -#include <cflib.h> +#include "charsets.h" +#include "private.h" +#include "ntlm.h" /* common func. for add/del/chk */ static int @@ -54,50 +56,68 @@ smbfs_keychain_cmn( uid_t uid, const char *dom, const char *usr, - const char *pass) + uchar_t *lmhash, + uchar_t *nthash) { smbioc_pk_t pk; - int err, fd; + int err, fd, sz; memset(&pk, 0, sizeof (pk)); - pk.pk_uid = uid; + err = 0; + fd = -1; switch (cmd) { case SMBIOC_PK_ADD: - if (pass == NULL) - return (SMB_KEYCHAIN_BADPASSWD); - if (strlcpy(pk.pk_pass, pass, sizeof (pk.pk_pass)) >= - sizeof (pk.pk_pass)) - return (SMB_KEYCHAIN_BADPASSWD); + /* + * Add password hashes to the keychain. + */ + if (lmhash == NULL || nthash == NULL) { + err = SMB_KEYCHAIN_BADPASSWD; + goto out; + } + memcpy(pk.pk_lmhash, lmhash, SMBIOC_HASH_SZ); + memcpy(pk.pk_nthash, nthash, SMBIOC_HASH_SZ); /* FALLTHROUGH */ case SMBIOC_PK_CHK: case SMBIOC_PK_DEL: - if (dom == NULL) - return (SMB_KEYCHAIN_BADDOMAIN); - if (strlcpy(pk.pk_dom, dom, sizeof (pk.pk_dom)) >= - sizeof (pk.pk_dom)) - return (SMB_KEYCHAIN_BADDOMAIN); - if (usr == NULL) - return (SMB_KEYCHAIN_BADUSER); - if (strlcpy(pk.pk_usr, usr, sizeof (pk.pk_usr)) >= - sizeof (pk.pk_usr)) - return (SMB_KEYCHAIN_BADUSER); + /* + * Copy domain and user. + */ + if (dom == NULL) { + err = SMB_KEYCHAIN_BADDOMAIN; + goto out; + } + sz = sizeof (pk.pk_dom); + if (strlcpy(pk.pk_dom, dom, sz) >= sz) { + err = SMB_KEYCHAIN_BADDOMAIN; + goto out; + } + if (usr == NULL) { + err = SMB_KEYCHAIN_BADUSER; + goto out; + } + sz = sizeof (pk.pk_usr); + if (strlcpy(pk.pk_usr, usr, sz) >= sz) { + err = SMB_KEYCHAIN_BADUSER; + goto out; + } break; case SMBIOC_PK_DEL_OWNER: /* all owned by the caller */ case SMBIOC_PK_DEL_EVERYONE: /* all owned by everyone */ /* * These two do not copyin any args, but we'll - * pass &pk here anyway just so we can use the + * pass pk here anyway just so we can use the * common code path below. */ break; default: - return (SMB_KEYCHAIN_UNKNOWN); + err = SMB_KEYCHAIN_UNKNOWN; + goto out; } fd = smb_open_driver(); @@ -107,28 +127,57 @@ smbfs_keychain_cmn( } err = 0; - if (ioctl(fd, cmd, &pk) < 0) + if (ioctl(fd, cmd, &pk) < 0) { err = errno; + goto out; + } + + if (cmd == SMBIOC_PK_CHK) { + if (lmhash != NULL) + memcpy(lmhash, pk.pk_lmhash, SMBIOC_HASH_SZ); + if (nthash != NULL) + memcpy(nthash, pk.pk_nthash, SMBIOC_HASH_SZ); + } - close(fd); out: - memset(&pk, 0, sizeof (pk)); + if (fd != -1) + close(fd); + return (err); } -/* Add a password to the keychain. */ +/* + * Add a password to the keychain. + * + * Note: pass is a cleartext password. + * We use it here to compute the LM hash and NT hash, + * and then store ONLY the hashes. + */ int smbfs_keychain_add(uid_t uid, const char *dom, const char *usr, const char *pass) { - return (smbfs_keychain_cmn(SMBIOC_PK_ADD, uid, dom, usr, pass)); + uchar_t lmhash[SMBIOC_HASH_SZ]; + uchar_t nthash[SMBIOC_HASH_SZ]; + int err, cmd = SMBIOC_PK_ADD; + + if (pass == NULL) + return (SMB_KEYCHAIN_BADPASSWD); + + if ((err = ntlm_compute_lm_hash(lmhash, pass)) != 0) + return (err); + if ((err = ntlm_compute_nt_hash(nthash, pass)) != 0) + return (err); + + err = smbfs_keychain_cmn(cmd, uid, dom, usr, lmhash, nthash); + return (err); } /* Delete a password from the keychain. */ int smbfs_keychain_del(uid_t uid, const char *dom, const char *usr) { - return (smbfs_keychain_cmn(SMBIOC_PK_DEL, uid, dom, usr, NULL)); + return (smbfs_keychain_cmn(SMBIOC_PK_DEL, uid, dom, usr, NULL, NULL)); } /* @@ -138,7 +187,22 @@ smbfs_keychain_del(uid_t uid, const char *dom, const char *usr) int smbfs_keychain_chk(const char *dom, const char *usr) { - return (smbfs_keychain_cmn(SMBIOC_PK_CHK, (uid_t)-1, dom, usr, NULL)); + uid_t uid = (uid_t)-1; + return (smbfs_keychain_cmn(SMBIOC_PK_CHK, uid, dom, usr, NULL, NULL)); +} + +/* + * Get the stored hashes + */ +int +smbfs_keychain_get(const char *dom, const char *usr, + uchar_t *lmhash, uchar_t *nthash) +{ + uid_t uid = (uid_t)-1; + int err, cmd = SMBIOC_PK_CHK; + + err = smbfs_keychain_cmn(cmd, uid, dom, usr, lmhash, nthash); + return (err); } /* @@ -147,7 +211,9 @@ smbfs_keychain_chk(const char *dom, const char *usr) int smbfs_keychain_del_owner() { - return (smbfs_keychain_cmn(SMBIOC_PK_DEL_OWNER, getuid(), 0, 0, 0)); + int cmd = SMBIOC_PK_DEL_OWNER; + uid_t uid = getuid(); + return (smbfs_keychain_cmn(cmd, uid, NULL, NULL, NULL, NULL)); } /* @@ -157,7 +223,51 @@ smbfs_keychain_del_owner() int smbfs_keychain_del_everyone() { - return (smbfs_keychain_cmn(SMBIOC_PK_DEL_EVERYONE, getuid(), 0, 0, 0)); + int cmd = SMBIOC_PK_DEL_EVERYONE; + uid_t uid = getuid(); + return (smbfs_keychain_cmn(cmd, uid, NULL, NULL, NULL, NULL)); +} + +/* + * Private function to get keychain p/w hashes. + */ +int +smb_get_keychain(struct smb_ctx *ctx) +{ + int err; + + if (ctx->ct_fullserver == NULL) { + DPRINT("ct_fullserver == NULL"); + return (EINVAL); + } + + /* + * 1st: try lookup using system name + */ + err = smbfs_keychain_get(ctx->ct_fullserver, ctx->ct_user, + ctx->ct_lmhash, ctx->ct_nthash); + if (!err) { + ctx->ct_flags |= SMBCF_KCFOUND; + DPRINT("found keychain entry for" + " server/user: %s/%s\n", + ctx->ct_fullserver, ctx->ct_user); + return (0); + } + + /* + * 2nd: try lookup using domain name + */ + err = smbfs_keychain_get(ctx->ct_domain, ctx->ct_user, + ctx->ct_lmhash, ctx->ct_nthash); + if (!err) { + ctx->ct_flags |= (SMBCF_KCFOUND | SMBCF_KCDOMAIN); + DPRINT("found keychain entry for" + " domain/user: %s/%s\n", + ctx->ct_domain, ctx->ct_user); + return (0); + } + + return (err); } @@ -173,27 +283,33 @@ int smbfs_default_dom_usr(const char *home, const char *server, char *dom, int maxdom, char *usr, int maxusr) { - struct smb_ctx sctx, *ctx = &sctx; + struct smb_ctx *ctx; int err; - err = smb_ctx_init(ctx, 0, NULL, SMBL_VC, SMBL_VC, SMB_ST_ANY); + err = smb_ctx_alloc(&ctx); if (err) return (err); + if (server) - smb_ctx_setserver(ctx, server); - if (home && *home) - ctx->ct_home = (char *)home; + smb_ctx_setfullserver(ctx, server); + + if (home && *home) { + if (ctx->ct_home) + free(ctx->ct_home); + ctx->ct_home = strdup(home); + } + err = smb_ctx_readrc(ctx); if (err) - return (err); - if (smb_rc) - rc_close(smb_rc); + goto out; if (dom) - strlcpy(dom, ctx->ct_ssn.ioc_workgroup, maxdom); + strlcpy(dom, ctx->ct_domain, maxdom); if (usr) - strlcpy(usr, ctx->ct_ssn.ioc_user, maxusr); + strlcpy(usr, ctx->ct_user, maxusr); - return (0); +out: + smb_ctx_free(ctx); + return (err); } diff --git a/usr/src/lib/libsmbfs/smb/krb5ssp.c b/usr/src/lib/libsmbfs/smb/krb5ssp.c new file mode 100644 index 0000000000..fbbd08398b --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/krb5ssp.c @@ -0,0 +1,535 @@ +/* + * 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. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Kerberos V Security Support Provider + * + * Based on code previously in ctx.c (from Boris Popov?) + * but then mostly rewritten at Sun. + */ + +#include <errno.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/mchain.h> + +#include "private.h" +#include "charsets.h" +#include "spnego.h" +#include "derparse.h" +#include "ssp.h" + +#include <kerberosv5/krb5.h> +#include <kerberosv5/com_err.h> + +/* RFC 1964 token ID codes */ +#define KRB_AP_REQ 1 +#define KRB_AP_REP 2 +#define KRB_ERROR 3 + +extern MECH_OID g_stcMechOIDList []; + +typedef struct krb5ssp_state { + /* Filled in by krb5ssp_init_client */ + krb5_context ss_krb5ctx; /* krb5 context (ptr) */ + krb5_ccache ss_krb5cc; /* credentials cache (ptr) */ + krb5_principal ss_krb5clp; /* client principal (ptr) */ + /* Filled in by krb5ssp_get_tkt */ + krb5_auth_context ss_auth; /* auth ctx. w/ server (ptr) */ +} krb5ssp_state_t; + + +/* + * adds a GSSAPI wrapper + */ +int +krb5ssp_tkt2gtok(uchar_t *tkt, ulong_t tktlen, + uchar_t **gtokp, ulong_t *gtoklenp) +{ + ulong_t len; + ulong_t bloblen = tktlen; + uchar_t krbapreq[2] = { KRB_AP_REQ, 0 }; + 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); + if ((blob = malloc(bloblen)) == NULL) { + DPRINT("malloc"); + return (ENOMEM); + } + + 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); + + assert(b + tktlen == blob + bloblen); + memcpy(b, tkt, tktlen); + *gtoklenp = bloblen; + *gtokp = blob; + return (0); +} + +/* + * 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 krb5.h */ + ENCTYPE_DES_CBC_MD5, + ENCTYPE_DES_CBC_CRC, + ENCTYPE_NULL +}; + +static const int rq_opts = + AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED; + +/* + * Obtain a kerberos ticket for the host we're connecting to. + * (This does the KRB_TGS exchange.) + */ +static int +krb5ssp_get_tkt(krb5ssp_state_t *ss, char *server, + uchar_t **tktp, ulong_t *tktlenp) +{ + krb5_context kctx = ss->ss_krb5ctx; + krb5_ccache kcc = ss->ss_krb5cc; + krb5_data indata = {0}; + krb5_data outdata = {0}; + krb5_error_code kerr = 0; + const char *fn = NULL; + uchar_t *tkt; + + /* Should have these from krb5ssp_init_client. */ + if (kctx == NULL || kcc == NULL) { + fn = "null kctx or kcc"; + kerr = EINVAL; + goto out; + } + + kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes); + if (kerr != 0) { + fn = "krb5_set_default_tgs_enctypes"; + goto out; + } + + /* Override the krb5 library default. */ + indata.data = ""; + + kerr = krb5_mk_req(kctx, &ss->ss_auth, rq_opts, "cifs", server, + &indata, kcc, &outdata); + if (kerr != 0) { + fn = "krb5_mk_req"; + goto out; + } + if ((tkt = malloc(outdata.length)) == NULL) { + kerr = ENOMEM; + fn = "malloc signing key"; + goto out; + } + memcpy(tkt, outdata.data, outdata.length); + *tktp = tkt; + *tktlenp = outdata.length; + kerr = 0; + +out: + if (kerr) { + if (fn == NULL) + fn = "?"; + DPRINT("%s err 0x%x: %s", fn, kerr, error_message(kerr)); + if (kerr <= 0 || kerr > ESTALE) + kerr = EAUTH; + } + + if (outdata.data) + krb5_free_data_contents(kctx, &outdata); + + /* Free kctx in krb5ssp_destroy */ + return (kerr); +} + + +/* + * Build an RFC 1964 KRB_AP_REQ message + * The caller puts on the SPNEGO wrapper. + */ +int +krb5ssp_put_request(struct ssp_ctx *sp, struct mbdata *out_mb) +{ + int err; + struct smb_ctx *ctx = sp->smb_ctx; + krb5ssp_state_t *ss = sp->sp_private; + uchar_t *tkt = NULL; + ulong_t tktlen; + uchar_t *gtok = NULL; /* gssapi token */ + ulong_t gtoklen; /* gssapi token length */ + char *prin = ctx->ct_srvname; + + if ((err = krb5ssp_get_tkt(ss, prin, &tkt, &tktlen)) != 0) + goto out; + if ((err = krb5ssp_tkt2gtok(tkt, tktlen, >ok, >oklen)) != 0) + goto out; + + if ((err = mb_init(out_mb, gtoklen)) != 0) + goto out; + if ((err = mb_put_mem(out_mb, gtok, gtoklen)) != 0) + goto out; + + if (ctx->ct_vcflags & SMBV_WILL_SIGN) + ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE; + +out: + if (gtok) + free(gtok); + if (tkt) + free(tkt); + + return (err); +} + +/* + * Unwrap a GSS-API encapsulated RFC 1964 reply message, + * i.e. type KRB_AP_REP or KRB_ERROR. + */ +int +krb5ssp_get_reply(struct ssp_ctx *sp, struct mbdata *in_mb) +{ + krb5ssp_state_t *ss = sp->sp_private; + mbuf_t *m = in_mb->mb_top; + int err = EBADRPC; + int dlen, rc; + long actual_len, token_len; + uchar_t *data; + krb5_data ap = {0}; + krb5_ap_rep_enc_part *reply = NULL; + + /* cheating: this mbuf is contiguous */ + assert(m->m_data == in_mb->mb_pos); + data = (uchar_t *)m->m_data; + dlen = m->m_len; + + /* + * Peel off the GSS-API wrapper. Looks like: + * AppToken: 60 81 83 + * OID(KRB5): 06 09 2a 86 48 86 f7 12 01 02 02 + * KRB_AP_REP: 02 00 + */ + rc = ASNDerCheckToken(data, SPNEGO_NEGINIT_APP_CONSTRUCT, + 0, dlen, &token_len, &actual_len); + if (rc != SPNEGO_E_SUCCESS) { + DPRINT("no AppToken? rc=0x%x", rc); + goto out; + } + if (dlen < actual_len) + goto out; + data += actual_len; + dlen -= actual_len; + + /* OID (KRB5) */ + rc = ASNDerCheckOID(data, spnego_mech_oid_Kerberos_V5, + dlen, &actual_len); + if (rc != SPNEGO_E_SUCCESS) { + DPRINT("no OID? rc=0x%x", rc); + goto out; + } + if (dlen < actual_len) + goto out; + data += actual_len; + dlen -= actual_len; + + /* KRB_AP_REP or KRB_ERROR */ + if (data[0] != KRB_AP_REP) { + DPRINT("KRB5 type: %d", data[1]); + goto out; + } + if (dlen < 2) + goto out; + data += 2; + dlen -= 2; + + /* + * Now what's left should be a krb5 reply + * NB: ap is NOT allocated, so don't free it. + */ + ap.length = dlen; + ap.data = (char *)data; + rc = krb5_rd_rep(ss->ss_krb5ctx, ss->ss_auth, &ap, &reply); + if (rc != 0) { + DPRINT("krb5_rd_rep: err 0x%x (%s)", + rc, error_message(rc)); + err = EAUTH; + goto out; + } + + /* + * Have the decoded reply. Save anything? + * + * NB: If this returns an error, we will get + * no more calls into this back-end module. + */ + err = 0; + +out: + if (reply != NULL) + krb5_free_ap_rep_enc_part(ss->ss_krb5ctx, reply); + if (err) + DPRINT("ret %d", err); + + return (err); +} + +/* + * krb5ssp_final + * + * Called after successful authentication. + * Setup the MAC key for signing. + */ +int +krb5ssp_final(struct ssp_ctx *sp) +{ + struct smb_ctx *ctx = sp->smb_ctx; + krb5ssp_state_t *ss = sp->sp_private; + krb5_keyblock *ssn_key = NULL; + int err, len; + + /* + * Save the session key, used for SMB signing + * and possibly other consumers (RPC). + */ + err = krb5_auth_con_getlocalsubkey( + ss->ss_krb5ctx, ss->ss_auth, &ssn_key); + if (err != 0) { + DPRINT("_getlocalsubkey, err=0x%x (%s)", + err, error_message(err)); + if (err <= 0 || err > ESTALE) + err = EAUTH; + goto out; + } + memset(ctx->ct_ssn_key, 0, SMBIOC_HASH_SZ); + if ((len = ssn_key->length) > SMBIOC_HASH_SZ) + len = SMBIOC_HASH_SZ; + memcpy(ctx->ct_ssn_key, ssn_key->contents, len); + + /* + * Set the MAC key on the first successful auth. + */ + if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) && + (ctx->ct_mackey == NULL)) { + ctx->ct_mackeylen = ssn_key->length; + ctx->ct_mackey = malloc(ctx->ct_mackeylen); + if (ctx->ct_mackey == NULL) { + ctx->ct_mackeylen = 0; + err = ENOMEM; + goto out; + } + memcpy(ctx->ct_mackey, ssn_key->contents, + ctx->ct_mackeylen); + /* + * Apparently, the server used seq. no. zero + * for our previous message, so next is two. + */ + ctx->ct_mac_seqno = 2; + } + err = 0; + +out: + if (ssn_key) + krb5_free_keyblock(ss->ss_krb5ctx, ssn_key); + + return (err); +} + +/* + * krb5ssp_next_token + * + * See ssp.c: ssp_ctx_next_token + */ +int +krb5ssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb, + struct mbdata *out_mb) +{ + int err; + + /* + * Note: in_mb == NULL on the first call. + */ + if (in_mb) { + err = krb5ssp_get_reply(sp, in_mb); + if (err) + goto out; + } + + if (out_mb) { + err = krb5ssp_put_request(sp, out_mb); + } else + err = krb5ssp_final(sp); + +out: + if (err) + DPRINT("ret: %d", err); + return (err); +} + +/* + * krb5ssp_ctx_destroy + * + * Destroy mechanism-specific data. + */ +void +krb5ssp_destroy(struct ssp_ctx *sp) +{ + krb5ssp_state_t *ss; + krb5_context kctx; + + ss = sp->sp_private; + if (ss == NULL) + return; + sp->sp_private = NULL; + + if ((kctx = ss->ss_krb5ctx) != NULL) { + /* from krb5ssp_get_tkt */ + if (ss->ss_auth) + krb5_auth_con_free(kctx, ss->ss_auth); + /* from krb5ssp_init_client */ + if (ss->ss_krb5clp) + krb5_free_principal(kctx, ss->ss_krb5clp); + if (ss->ss_krb5cc) + krb5_cc_close(kctx, ss->ss_krb5cc); + krb5_free_context(kctx); + } + + free(ss); +} + +/* + * krb5ssp_init_clnt + * + * Initialize a new Kerberos SSP client context. + * + * The user must already have a TGT in their credential cache, + * as shown by the "klist" command. + */ +int +krb5ssp_init_client(struct ssp_ctx *sp) +{ + krb5ssp_state_t *ss; + krb5_error_code kerr; + krb5_context kctx = NULL; + krb5_ccache kcc = NULL; + krb5_principal kprin = NULL; + + if ((sp->smb_ctx->ct_authflags & SMB_AT_KRB5) == 0) { + DPRINT("KRB5 not in authflags"); + return (ENOTSUP); + } + + ss = calloc(1, sizeof (*ss)); + if (ss == NULL) + return (ENOMEM); + + sp->sp_nexttok = krb5ssp_next_token; + sp->sp_destroy = krb5ssp_destroy; + sp->sp_private = ss; + + kerr = krb5_init_context(&kctx); + if (kerr) { + DPRINT("krb5_init_context, kerr 0x%x", kerr); + goto errout; + } + ss->ss_krb5ctx = kctx; + + /* non-default would instead use krb5_cc_resolve */ + kerr = krb5_cc_default(kctx, &kcc); + if (kerr) { + DPRINT("krb5_cc_default, kerr 0x%x", kerr); + goto errout; + } + ss->ss_krb5cc = kcc; + + /* + * Get the client principal (ticket), + * or discover that we don't have one. + */ + kerr = krb5_cc_get_principal(kctx, kcc, &kprin); + if (kerr) { + DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr); + goto errout; + } + ss->ss_krb5clp = kprin; + + /* Success! */ + DPRINT("Ticket cache: %s:%s", + krb5_cc_get_type(kctx, kcc), + krb5_cc_get_name(kctx, kcc)); + return (0); + +errout: + krb5ssp_destroy(sp); + return (ENOTSUP); +} diff --git a/usr/src/lib/libsmbfs/smb/llib-lsmbfs b/usr/src/lib/libsmbfs/smb/llib-lsmbfs index 05e1967055..e8e05e4272 100644 --- a/usr/src/lib/libsmbfs/smb/llib-lsmbfs +++ b/usr/src/lib/libsmbfs/smb/llib-lsmbfs @@ -20,13 +20,18 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /*LINTLIBRARY*/ /*PROTOLIB1*/ +#include <netsmb/smbfs_api.h> +#include <netsmb/smbfs_acl.h> + #include <netsmb/smb_lib.h> +#include <netsmb/smb_keychain.h> +#include <netsmb/smb_netshareenum.h> +#include <netsmb/smb_rap.h> + diff --git a/usr/src/lib/libsmbfs/smb/mapfile-vers b/usr/src/lib/libsmbfs/smb/mapfile-vers index f927c7bcb8..3486333120 100644 --- a/usr/src/lib/libsmbfs/smb/mapfile-vers +++ b/usr/src/lib/libsmbfs/smb/mapfile-vers @@ -36,7 +36,7 @@ # MAPFILE HEADER END # -SUNWprivate_1.0 { +SUNWprivate { global: convert_leunicode_to_utf8; convert_unicode_to_utf8; @@ -58,27 +58,37 @@ SUNWprivate_1.0 { nls_str_toloc; nls_str_upper; - rc_close; - rc_open; + smb_close_rcfile; + smb_ctx_alloc; smb_ctx_done; smb_ctx_flags2; + smb_ctx_free; + smb_ctx_get_ssn; + smb_ctx_get_ssnkey; + smb_ctx_get_tree; + smb_ctx_gethandle; smb_ctx_init; - smb_ctx_lookup; + smb_ctx_kill; smb_ctx_opt; + smb_ctx_parseunc; smb_ctx_readrc; smb_ctx_resolve; + smb_ctx_scan_argv; smb_ctx_set_close_hook; + smb_ctx_setauthflags; + smb_ctx_setdomain; smb_ctx_setfullserver; smb_ctx_setpassword; + smb_ctx_setpwhash; + smb_ctx_setscope; smb_ctx_setserver; smb_ctx_setshare; smb_ctx_setsrvaddr; smb_ctx_setuser; - smb_ctx_setworkgroup; + smb_ctx_setwins; - smb_ctx_tdis; smb_debug = NODIRECT; # data smb_error; # @@ -89,14 +99,20 @@ SUNWprivate_1.0 { smb_fh_write; smb_fh_xactnp; # + smb_get_authentication; smb_getprogname; + smb_iod_connect; + smb_iod_door_path; + smb_iod_open_door; + smb_iod_work; smb_lib_init; smb_netshareenum; # will move to libnetapi smb_open_rcfile; + smb_printer_open; + smb_printer_close; smb_simplecrypt; smb_simpledecrypt; smb_strerror; - smb_rc = NODIRECT; # data # # Functions to support the Remote Access Protocol (RAP) smb_rap_create; @@ -126,7 +142,7 @@ SUNWprivate_1.0 { smbfs_keychain_del_everyone; smbfs_keychain_del_owner; - unpercent; + smbutil_std_opts; local: *; }; diff --git a/usr/src/lib/libsmbfs/smb/mbuf.c b/usr/src/lib/libsmbfs/smb/mbuf.c index 2e995dad50..9380ec12e9 100644 --- a/usr/src/lib/libsmbfs/smb/mbuf.c +++ b/usr/src/lib/libsmbfs/smb/mbuf.c @@ -33,7 +33,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -47,17 +47,11 @@ #include <libintl.h> #include <assert.h> -#include <netsmb/smb.h> #include <netsmb/smb_lib.h> #include <netsmb/mchain.h> #include "private.h" - -#ifdef APPLE -#define __func__ "" -#define MBERROR(format, args...) \ - printf("%s(%d): "format, __func__, __LINE__, ## args) -#endif +#include "charsets.h" static int m_get(size_t len, struct mbuf **mpp) @@ -85,7 +79,7 @@ m_free(struct mbuf *m) free(m); } -static void +void m_freem(struct mbuf *m0) { struct mbuf *m; @@ -97,7 +91,7 @@ m_freem(struct mbuf *m0) } } -static size_t +size_t m_totlen(struct mbuf *m0) { struct mbuf *m = m0; @@ -226,61 +220,64 @@ int mb_put_uint8(struct mbdata *mbp, uint8_t x) { uint8_t y = x; - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int mb_put_uint16be(struct mbdata *mbp, uint16_t x) { uint16_t y = htobes(x); - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int mb_put_uint16le(struct mbdata *mbp, uint16_t x) { uint16_t y = htoles(x); - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int mb_put_uint32be(struct mbdata *mbp, uint32_t x) { uint32_t y = htobel(x); - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int mb_put_uint32le(struct mbdata *mbp, uint32_t x) { uint32_t y = htolel(x); - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int mb_put_uint64be(struct mbdata *mbp, uint64_t x) { uint64_t y = htobeq(x); - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int mb_put_uint64le(struct mbdata *mbp, uint64_t x) { uint64_t y = htoleq(x); - return (mb_put_mem(mbp, (char *)&y, sizeof (y))); + return (mb_put_mem(mbp, &y, sizeof (y))); } int -mb_put_mem(struct mbdata *mbp, const char *source, size_t size) +mb_put_mem(struct mbdata *mbp, const void *vmem, size_t size) { struct mbuf *m; + const char *src; char *dst; size_t cplen; int error; if (size == 0) return (0); + + src = vmem; m = mbp->mb_cur; if ((error = m_getm(m, size, &m)) != 0) return (error); @@ -293,9 +290,9 @@ mb_put_mem(struct mbdata *mbp, const char *source, size_t size) if (cplen > size) cplen = size; dst = mtod(m, char *) + m->m_len; - if (source) { - bcopy(source, dst, cplen); - source += cplen; + if (src) { + bcopy(src, dst, cplen); + src += cplen; } else bzero(dst, cplen); size -= cplen; @@ -307,10 +304,26 @@ mb_put_mem(struct mbdata *mbp, const char *source, size_t size) return (0); } +/* + * Append another mbuf to the mbuf chain. + * If what we're appending is smaller than + * the current trailing space, just copy. + * This always consumes the passed mbuf. + */ int mb_put_mbuf(struct mbdata *mbp, struct mbuf *m) { - mbp->mb_cur->m_next = m; + struct mbuf *cm = mbp->mb_cur; + int ts = M_TRAILINGSPACE(cm); + + if (m->m_next == NULL && m->m_len <= ts) { + /* just copy */ + mb_put_mem(mbp, m->m_data, m->m_len); + m_freem(m); + return (0); + } + + cm->m_next = m; while (m) { mbp->mb_count += m->m_len; if (m->m_next == NULL) @@ -322,17 +335,62 @@ mb_put_mbuf(struct mbdata *mbp, struct mbuf *m) return (0); } +/* + * Convenience function to put an OEM or Unicode string, + * null terminated, and aligned if necessary. + */ int -mb_put_pstring(struct mbdata *mbp, const char *s) +mb_put_dstring(struct mbdata *mbp, const char *s, int uc) { - int error, len = strlen(s); - - if (len > 255) { - len = 255; + int err; + + if (uc) { + /* Put Unicode. align(2) first. */ + if (mbp->mb_count & 1) + mb_put_uint8(mbp, 0); + err = mb_put_ustring(mbp, s); + } else { + /* Put ASCII (really OEM) */ + err = mb_put_astring(mbp, s); } - if ((error = mb_put_uint8(mbp, len)) != 0) - return (error); - return (mb_put_mem(mbp, s, len)); + + return (err); +} + +/* + * Put an ASCII string (really OEM), given a UTF-8 string. + */ +int +mb_put_astring(struct mbdata *mbp, const char *s) +{ + char *abuf; + int err, len; + + abuf = convert_utf8_to_wincs(s); + if (abuf == NULL) + return (ENOMEM); + len = strlen(abuf) + 1; + err = mb_put_mem(mbp, abuf, len); + free(abuf); + return (err); +} + +/* + * Put UCS-2LE, given a UTF-8 string. + */ +int +mb_put_ustring(struct mbdata *mbp, const char *s) +{ + uint16_t *ubuf; + int err, len; + + ubuf = convert_utf8_to_leunicode(s); + if (ubuf == NULL) + return (ENOMEM); + len = unicode_strlen(ubuf) + 1; + err = mb_put_mem(mbp, ubuf, (len << 1)); + free(ubuf); + return (err); } /* @@ -343,111 +401,114 @@ mb_put_pstring(struct mbdata *mbp, const char *s) int mb_get_uint8(struct mbdata *mbp, uint8_t *x) { - return (mb_get_mem(mbp, (char *)x, 1)); + return (mb_get_mem(mbp, x, 1)); } int mb_get_uint16(struct mbdata *mbp, uint16_t *x) { - return (mb_get_mem(mbp, (char *)x, 2)); + return (mb_get_mem(mbp, x, 2)); } int mb_get_uint16le(struct mbdata *mbp, uint16_t *x) { uint16_t v; - int error = mb_get_uint16(mbp, &v); + int err; + if ((err = mb_get_mem(mbp, &v, 2)) != 0) + return (err); if (x != NULL) *x = letohs(v); - return (error); + return (0); } int mb_get_uint16be(struct mbdata *mbp, uint16_t *x) { uint16_t v; - int error = mb_get_uint16(mbp, &v); + int err; + if ((err = mb_get_mem(mbp, &v, 2)) != 0) + return (err); if (x != NULL) *x = betohs(v); - return (error); + return (0); } int mb_get_uint32(struct mbdata *mbp, uint32_t *x) { - return (mb_get_mem(mbp, (char *)x, 4)); + return (mb_get_mem(mbp, x, 4)); } int mb_get_uint32be(struct mbdata *mbp, uint32_t *x) { uint32_t v; - int error; + int err; - error = mb_get_uint32(mbp, &v); + if ((err = mb_get_mem(mbp, &v, 4)) != 0) + return (err); if (x != NULL) *x = betohl(v); - return (error); + return (0); } int mb_get_uint32le(struct mbdata *mbp, uint32_t *x) { uint32_t v; - int error; + int err; - error = mb_get_uint32(mbp, &v); + if ((err = mb_get_mem(mbp, &v, 4)) != 0) + return (err); if (x != NULL) *x = letohl(v); - return (error); + return (0); } int mb_get_uint64(struct mbdata *mbp, uint64_t *x) { - return (mb_get_mem(mbp, (char *)x, 8)); + return (mb_get_mem(mbp, x, 8)); } int mb_get_uint64be(struct mbdata *mbp, uint64_t *x) { uint64_t v; - int error; + int err; - error = mb_get_uint64(mbp, &v); + if ((err = mb_get_mem(mbp, &v, 8)) != 0) + return (err); if (x != NULL) *x = betohq(v); - return (error); + return (0); } int mb_get_uint64le(struct mbdata *mbp, uint64_t *x) { uint64_t v; - int error; + int err; - error = mb_get_uint64(mbp, &v); + if ((err = mb_get_mem(mbp, &v, 8)) != 0) + return (err); if (x != NULL) *x = letohq(v); - return (error); + return (0); } int -mb_get_mem(struct mbdata *mbp, char *target, size_t size) +mb_get_mem(struct mbdata *mbp, void *vmem, size_t size) { struct mbuf *m = mbp->mb_cur; + char *dst = vmem; uint_t count; while (size > 0) { if (m == NULL) { -#ifdef DEBUG - printf( - dgettext(TEXT_DOMAIN, "incomplete copy\n")); -#endif -#ifdef APPLE - MBERROR("incomplete copy\n"); -#endif + /* DPRINT("incomplete copy"); */ return (EBADRPC); } count = mb_left(m, mbp->mb_pos); @@ -460,15 +521,181 @@ mb_get_mem(struct mbdata *mbp, char *target, size_t size) if (count > size) count = size; size -= count; - if (target) { + if (dst) { if (count == 1) { - *target++ = *mbp->mb_pos; + *dst++ = *mbp->mb_pos; } else { - bcopy(mbp->mb_pos, target, count); - target += count; + bcopy(mbp->mb_pos, dst, count); + dst += count; } } mbp->mb_pos += count; } return (0); } + +/* + * Get the next SIZE bytes as a separate mblk. + * Nothing fancy here - just copy. + */ +int +mb_get_mbuf(struct mbdata *mbp, int size, struct mbuf **ret) +{ + mbuf_t *m; + int err; + + err = m_get(size, &m); + if (err) + return (err); + + err = mb_get_mem(mbp, m->m_data, size); + if (err) { + m_freem(m); + return (err); + } + m->m_len = size; + *ret = m; + + return (0); +} + +/* + * Get a string from the mbuf chain, + * either Unicode or OEM chars. + */ +int +mb_get_string(struct mbdata *mbp, char **str_pp, int uc) +{ + int err; + + if (uc) + err = mb_get_ustring(mbp, str_pp); + else + err = mb_get_astring(mbp, str_pp); + return (err); +} + +/* + * Get an ASCII (really OEM) string from the mbuf chain + * and convert it to UTF-8 + * Similar to mb_get_ustring below. + */ +int +mb_get_astring(struct mbdata *real_mbp, char **str_pp) +{ + struct mbdata tmp_mb, *mbp; + char *tstr, *ostr; + int err, i, slen; + uint8_t ch; + + /* + * First, figure out the string length. + * Use a copy of the real_mbp so we don't + * actually consume it here, then search for + * the null (or end of data). + */ + bcopy(real_mbp, &tmp_mb, sizeof (tmp_mb)); + mbp = &tmp_mb; + slen = 0; + for (;;) { + err = mb_get_uint8(mbp, &ch); + if (err) + break; + if (ch == 0) + break; + slen++; + } + + /* + * Now read the (OEM) string for real. + * No need to re-check errors. + */ + tstr = malloc(slen + 1); + if (tstr == NULL) + return (ENOMEM); + mbp = real_mbp; + for (i = 0; i < slen; i++) { + mb_get_uint8(mbp, &ch); + tstr[i] = ch; + } + tstr[i] = 0; + mb_get_uint8(mbp, NULL); + + /* + * Convert OEM to UTF-8 + */ + ostr = convert_wincs_to_utf8(tstr); + free(tstr); + if (ostr == NULL) + return (ENOMEM); + + *str_pp = ostr; + return (0); +} + +/* + * Get a UCS-2LE string from the mbuf chain, and + * convert it to UTF-8. + * + * Similar to mb_get_astring below. + */ +int +mb_get_ustring(struct mbdata *real_mbp, char **str_pp) +{ + struct mbdata tmp_mb, *mbp; + uint16_t *tstr; + char *ostr; + int err, i, slen; + uint16_t ch; + + /* + * First, align(2) on the real_mbp + */ + if (((uintptr_t)real_mbp->mb_pos) & 1) + mb_get_uint8(real_mbp, NULL); + + /* + * Next, figure out the string length. + * Use a copy of the real_mbp so we don't + * actually consume it here, then search for + * the null (or end of data). + */ + bcopy(real_mbp, &tmp_mb, sizeof (tmp_mb)); + mbp = &tmp_mb; + slen = 0; + for (;;) { + err = mb_get_uint16le(mbp, &ch); + if (err) + break; + if (ch == 0) + break; + slen++; + } + + /* + * Now read the (UCS-2) string for real. + * No need to re-check errors. Note: + * This puts the UCS-2 in NATIVE order! + */ + tstr = calloc(slen + 1, 2); + if (tstr == NULL) + return (ENOMEM); + mbp = real_mbp; + for (i = 0; i < slen; i++) { + mb_get_uint16le(mbp, &ch); + tstr[i] = ch; + } + tstr[i] = 0; + mb_get_uint16le(mbp, NULL); + + /* + * Convert UCS-2 (native!) to UTF-8 + */ + ostr = convert_unicode_to_utf8(tstr); + free(tstr); + if (ostr == NULL) + return (ENOMEM); + + *str_pp = ostr; + return (0); +} diff --git a/usr/src/lib/libsmbfs/smb/nb.c b/usr/src/lib/libsmbfs/smb/nb.c index f60ae0b314..f280420c54 100644 --- a/usr/src/lib/libsmbfs/smb/nb.c +++ b/usr/src/lib/libsmbfs/smb/nb.c @@ -32,29 +32,61 @@ * $Id: nb.c,v 1.1.1.2 2001/07/06 22:38:42 conrad Exp $ */ -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ #include <sys/param.h> #include <sys/socket.h> -#include <ctype.h> -#include <netdb.h> #include <errno.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> -#include <stdio.h> #include <unistd.h> #include <libintl.h> +#include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> +#include <cflib.h> #include <netsmb/netbios.h> #include <netsmb/smb_lib.h> #include <netsmb/nb_lib.h> -#include <cflib.h> +int nb_ctx_setwins(struct nb_ctx *, const char *, const char *); + + +/* + * API for library consumer to set wins1, wins2 + */ +int +smb_ctx_setwins(struct smb_ctx *ctx, const char *wins1, const char *wins2) +{ + struct nb_ctx *nb = ctx->ct_nb; + + if (nb == NULL) + return (EINVAL); + + return (nb_ctx_setwins(nb, wins1, wins2)); +} + +/* + * API for library consumer to set NB scope. + */ +int +smb_ctx_setscope(struct smb_ctx *ctx, const char *scope) +{ + struct nb_ctx *nb = ctx->ct_nb; + + if (nb == NULL) + return (EINVAL); + + return (nb_ctx_setscope(nb, scope)); +} int nb_ctx_create(struct nb_ctx **ctxpp) @@ -81,36 +113,37 @@ nb_ctx_done(struct nb_ctx *ctx) free(ctx); } -static int -nb_ctx_setwins(in_addr_t *ina_p, const char *str) +int +nb_ctx_setwins(struct nb_ctx *ctx, const char *wins1, const char *wins2) { struct in_addr ina; - struct sockaddr *sap; int error; - if (str == NULL || str[0] == 0) - return (EINVAL); + if (wins1 == NULL) { + ctx->nb_wins1 = 0; + ctx->nb_wins2 = 0; + return (0); + } - if (inet_aton(str, &ina)) { - *ina_p = ina.s_addr; - } else { - error = nb_resolvehost_in(str, &sap); + error = nb_resolvehost_in(wins1, &ina); + if (error) { + smb_error(dgettext(TEXT_DOMAIN, "can't resolve %s"), + error, wins1); + return (error); + } + ctx->nb_wins1 = ina.s_addr; + + if (wins2 == NULL) + ctx->nb_wins2 = 0; + else { + error = nb_resolvehost_in(wins2, &ina); if (error) { smb_error(dgettext(TEXT_DOMAIN, "can't resolve %s"), - error, str); + error, wins2); return (error); } - if (sap->sa_family != AF_INET) { - smb_error(dgettext(TEXT_DOMAIN, - "unsupported address family %d"), 0, - sap->sa_family); - return (EINVAL); - } - /*LINTED*/ - *ina_p = ((struct sockaddr_in *)sap)->sin_addr.s_addr; - free(sap); + ctx->nb_wins2 = ina.s_addr; } - return (0); } @@ -126,10 +159,9 @@ nb_ctx_setns(struct nb_ctx *ctx, const char *addr) { int error; - error = nb_ctx_setwins(&ctx->nb_wins1, addr); + error = nb_ctx_setwins(ctx, addr, NULL); if (error) return (error); - ctx->nb_wins2 = 0; /* Deal with explicit request for broadcast. */ if (ctx->nb_wins1 == INADDR_BROADCAST) { @@ -182,23 +214,35 @@ int nb_ctx_readrcsection(struct rcfile *rcfile, struct nb_ctx *ctx, const char *sname, int level) { - char *p; + char *wins1, *wins2; int error; int nbns_enable; int nbns_broadcast; if (level > 1) return (EINVAL); + + /* External callers pass NULL to get the default. */ + if (rcfile == NULL) + rcfile = smb_rc; + #ifdef NOT_DEFINED rc_getint(rcfile, sname, "nbtimeout", &ctx->nb_timo); rc_getstringptr(rcfile, sname, "nbscope", &p); if (p) nb_ctx_setscope(ctx, p); #endif - /* "nbns" will be "wins1" some day, and we'll have a "wins2" also */ - rc_getstringptr(rcfile, sname, "nbns", &p); - if (p) { - error = nb_ctx_setwins(&ctx->nb_wins1, p); + /* + * Get "wins1", "wins2" config strings. + * Also support legacy "nbns". + */ + rc_getstringptr(rcfile, sname, "wins1", &wins1); + if (wins1 == NULL) + rc_getstringptr(rcfile, sname, "nbns", &wins1); + rc_getstringptr(rcfile, sname, "wins2", &wins2); + + if (wins1 != NULL) { + error = nb_ctx_setwins(ctx, wins1, wins2); if (error) { smb_error(dgettext(TEXT_DOMAIN, "invalid address specified in the section %s"), diff --git a/usr/src/lib/libsmbfs/smb/nb_name.c b/usr/src/lib/libsmbfs/smb/nb_name.c index af13efa7b4..604d9142cd 100644 --- a/usr/src/lib/libsmbfs/smb/nb_name.c +++ b/usr/src/lib/libsmbfs/smb/nb_name.c @@ -50,7 +50,7 @@ #include "private.h" int -nb_snballoc(int namelen, struct sockaddr_nb **dst) +nb_snballoc(struct sockaddr_nb **dst) { struct sockaddr_nb *snb; int slen; @@ -73,6 +73,9 @@ nb_snbfree(struct sockaddr *snb) /* * Create a full NETBIOS address + * Passed names should already be upper case. + * Stores the names truncated or blank padded. + * NetBIOS name encoding happens later. */ int nb_sockaddr(struct sockaddr *peer, struct nb_name *np, @@ -81,53 +84,23 @@ nb_sockaddr(struct sockaddr *peer, struct nb_name *np, { struct sockaddr_nb *snb; struct sockaddr_in *sin; - struct hostent *hst; - int nmlen, error; + int error; if (peer && (peer->sa_family != AF_INET)) return (EPROTONOSUPPORT); -#if NOT_DEFINED /* moved encoding into kernel */ - nmlen = nb_name_len(np); - if (nmlen < NB_ENCNAMELEN) - return (EINVAL); -#else - nmlen = NB_NAMELEN; -#endif - error = nb_snballoc(nmlen, &snb); + error = nb_snballoc(&snb); if (error) return (error); - /* - * Moved toupper() work to callers. - * - * Moved NetBIOS name encoding into the driver - * so we have readable names right up until the - * point where we marshall them in to a message. - * Just makes debugging easier. - */ -#if NOT_DEFINED - if (nmlen != nb_name_encode(np, snb->snb_name)) - printf(dgettext(TEXT_DOMAIN, - "a bug somewhere in the nb_name* code\n")); - /* XXX */ -#else - /* - * OK, nb_snballoc() did bzero, set snb_family. - * Hacks for "*" moved here from nb_name_encode(), - * but belongs where nn_name is filled in... - * XXX fix later - */ if (strcmp(np->nn_name, "*") == 0) { /* Star is special: No blanks, type, etc. */ snb->snb_name[0] = '*'; } else { /* Normal name: pad with blanks, add type. */ - assert(NB_NAMELEN == 16); snprintf(snb->snb_name, NB_NAMELEN, "%-15.15s", np->nn_name); snb->snb_name[15] = (char)np->nn_type; } -#endif if (peer) { /*LINTED*/ @@ -182,38 +155,20 @@ nb_encname_len(const uchar_t *str) } int -nb_name_encode(struct nb_name *np, uchar_t *dst) +nb_name_encode(struct mbdata *mbp, struct nb_name *nn) { - char *name; - uchar_t *plen; - uchar_t ch, *cp = dst; - char *p, buf1[NB_NAMELEN+1]; + char *plen; + uchar_t ch; + char *p, namebuf[NB_NAMELEN+1]; int i, lblen; - /* - * XXX: I'd rather see this part moved into - * callers of this function, leaving just - * the pure NB encoding here. -GWR - */ - name = np->nn_name; - if (name[0] == '*') { - /* Star is special: No blanks, type, etc. */ - bzero(buf1, NB_NAMELEN); - buf1[0] = '*'; - } else { - /* Normal name: pad with blanks, add type. */ - assert(NB_NAMELEN == 16); - snprintf(buf1, NB_NAMELEN, - "%-15.15s", name); - buf1[15] = (char)np->nn_type; - } - name = buf1; + bcopy(nn->nn_name, namebuf, NB_NAMELEN); + namebuf[NB_NAMELEN-1] = (char)nn->nn_type; + namebuf[NB_NAMELEN] = '\0'; /* for debug */ /* * Do the NetBIOS "first-level encoding" here. - * (RFC1002 explains this wierdness...) - * See similar code in kernel nsmb module: - * uts/common/fs/smbclnt/netsmb/smb_trantcp.c + * (RFC1002 explains this weirdness...) * * Here is what we marshall: * uint8_t NAME_LENGTH (always 32) @@ -223,13 +178,13 @@ nb_name_encode(struct nb_name *np, uchar_t *dst) */ /* NAME_LENGTH */ - *cp++ = (2 * NB_NAMELEN); + mb_put_uint8(mbp, (2 * NB_NAMELEN)); /* ENCODED_NAME */ for (i = 0; i < NB_NAMELEN; i++) { - ch = name[i]; - *cp++ = 'A' + ((ch >> 4) & 0xF); - *cp++ = 'A' + ((ch) & 0xF); + ch = namebuf[i]; + mb_put_uint8(mbp, 'A' + ((ch >> 4) & 0xF)); + mb_put_uint8(mbp, 'A' + ((ch) & 0xF)); } /* @@ -241,32 +196,38 @@ nb_name_encode(struct nb_name *np, uchar_t *dst) * start of each string. This keeps a pointer * to the location and fills it in after the * length of the string is determined. + * + * One string of length zero terminates. + * With no scope string, the zero-length + * string is the only thing there. */ -#if NOT_DEFINED /* XXX: not yet */ - if (np->nn_scope) { - plen = cp++; - *plen = 0; /* fill in later */ - lblen = 0; - for (p = np->nn_scope; ; p++) { - if (*p == '.' || *p == 0) { - *plen = lblen; - if (*p == 0) - break; - plen = cp++; - *plen = 0; - lblen = 0; - } else { - if (lblen < NB_MAXLABLEN) { - *cp++ = *p; - lblen++; - } + if (nn->nn_scope == NULL) { + mb_put_uint8(mbp, 0); + return (0); + } + + mb_fit(mbp, 1, &plen); + *plen = 0; /* will update below */ + lblen = 0; + for (p = nn->nn_scope; ; p++) { + if (*p == '\0') { + *plen = lblen; + if (lblen) + mb_put_uint8(mbp, 0); + break; + } + if (*p == '.') { + *plen = lblen; + mb_fit(mbp, 1, &plen); + *plen = 0; + lblen = 0; + } else { + if (lblen < NB_MAXLABLEN) { + mb_put_uint8(mbp, *p); + lblen++; } } - } else -#endif /* XXX: not yet */ - { - *cp++ = 0; } - return (cp - dst); + return (0); } diff --git a/usr/src/lib/libsmbfs/smb/nb_net.c b/usr/src/lib/libsmbfs/smb/nb_net.c index 29109c3093..ec0db6cffc 100644 --- a/usr/src/lib/libsmbfs/smb/nb_net.c +++ b/usr/src/lib/libsmbfs/smb/nb_net.c @@ -38,13 +38,14 @@ #include <sys/sockio.h> #include <net/if.h> #include <ctype.h> -#include <netdb.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <stdio.h> #include <unistd.h> +#include <netdb.h> +#include <nss_dbdefs.h> #include <err.h> @@ -59,32 +60,14 @@ */ int -nb_getlocalname(char *name, size_t maxlen) -{ - char buf[1024], *cp; - - if (gethostname(buf, sizeof (buf)) != 0) - return (errno); - cp = strchr(buf, '.'); - if (cp) - *cp = 0; - strlcpy(name, buf, maxlen); - return (0); -} - -int -nb_resolvehost_in(const char *name, struct sockaddr **dest) +nb_resolvehost_in(const char *name, struct in_addr *ia) { - struct hostent *h; - struct sockaddr_in *sinp; - in_addr_t addr; - struct in_addr in; - int len; - char **p; + char he_buf[NSS_BUFLEN_HOSTS]; + struct hostent he, *h; + int err; - - h = gethostbyname(name); - if (!h) { + h = gethostbyname_r(name, &he, he_buf, sizeof (he_buf), &err); + if (h == NULL) { #ifdef DEBUG warnx("can't get server address `%s': ", name); #endif @@ -102,19 +85,7 @@ nb_resolvehost_in(const char *name, struct sockaddr **dest) #endif return (EAFNOSUPPORT); } - len = sizeof (struct sockaddr_in); - sinp = malloc(len); - if (sinp == NULL) - return (ENOMEM); - bzero(sinp, len); - /* - * There is no sin_len in sockaddr_in structure on Solaris. - * sinp->sin_len = len; - */ - sinp->sin_family = h->h_addrtype; - memcpy(&sinp->sin_addr.s_addr, *h->h_addr_list,\ - sizeof (sinp->sin_addr.s_addr)); - sinp->sin_port = htons(SMB_TCP_PORT); - *dest = (struct sockaddr *)sinp; + + memcpy(ia, h->h_addr, sizeof (*ia)); return (0); } diff --git a/usr/src/lib/libsmbfs/smb/nb_ssn.c b/usr/src/lib/libsmbfs/smb/nb_ssn.c new file mode 100644 index 0000000000..bd53ce6fce --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/nb_ssn.c @@ -0,0 +1,330 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * NetBIOS session service functions + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/poll.h> + +#include <netsmb/netbios.h> +#include <netsmb/smb_lib.h> +#include <netsmb/nb_lib.h> +#include <netsmb/mchain.h> + +#include "private.h" +#include "charsets.h" + +static int nb_ssn_send(struct smb_ctx *, struct mbdata *, int, int); +static int nb_ssn_recv(struct smb_ctx *, struct mbdata *, int *, int *); +static int nb_ssn_pollin(struct smb_ctx *, int); + +/* + * Send a data message. + */ +int +smb_ssn_send(struct smb_ctx *ctx, struct mbdata *mbp) +{ + return (nb_ssn_send(ctx, mbp, 0, mbp->mb_count)); +} + +/* + * Send a NetBIOS message, after + * prepending the 4-byte header. + */ +static int +nb_ssn_send(struct smb_ctx *ctx, struct mbdata *mbp, + int mtype, int mlen) +{ + mbuf_t *m = mbp->mb_top; + int fd = ctx->ct_tran_fd; + int err, flags; + uint32_t hdr, hdrbuf; + + if (m == NULL) + return (EINVAL); + + /* + * Prepend the NetBIOS header. + * Using mbuf trickery to ensure it's + * not separated from the body. + */ + hdr = (mtype << 24) | mlen; + hdrbuf = htonl(hdr); + m->m_data -= 4; + m->m_len += 4; + bcopy(&hdrbuf, m->m_data, 4); + + /* Send it. */ + while (m) { + flags = (m->m_next) ? T_MORE : T_PUSH; + if (t_snd(fd, m->m_data, m->m_len, flags) < 0) { + if (t_errno == TSYSERR) + err = errno; + else + err = EPROTO; + DPRINT("t_snd: t_errno %d, err %d", t_errno, err); + return (err); + } + m = m->m_next; + } + return (0); +} + +/* + * Receive a data message. Discard anything else. + * Caller must deal with EAGAIN, EINTR. + */ +int +smb_ssn_recv(struct smb_ctx *ctx, struct mbdata *mbp) +{ + int err, mtype, mlen; + err = nb_ssn_recv(ctx, mbp, &mtype, &mlen); + if (err) + return (err); + if (mtype != NB_SSN_MESSAGE) { + DPRINT("discard type 0x%x", mtype); + mb_done(mbp); + return (EAGAIN); + } + if (mlen == 0) { + DPRINT("zero length"); + mb_done(mbp); + return (EAGAIN); + } + + return (0); +} + +/* + * Receive a NetBIOS message, any type. + * Give caller type and length. + */ +static int +nb_ssn_recv(struct smb_ctx *ctx, struct mbdata *mb, + int *mtype, int *mlen) +{ + char *buf; + uint32_t hdr, hdrbuf; + int cnt, len, err, moreflag; + int fd = ctx->ct_tran_fd; + int tmo = smb_recv_timeout * 1000; + + /* + * Start by getting the header + * (four bytes) + */ + if ((err = nb_ssn_pollin(ctx, tmo)) != 0) { + DPRINT("pollin err %d", err); + return (err); + } + moreflag = 0; + cnt = t_rcv(fd, &hdrbuf, sizeof (hdrbuf), &moreflag); + if (cnt < 0) { + err = get_xti_err(fd); + DPRINT("t_errno %d err %d", t_errno, err); + return (err); + } + + if (cnt != sizeof (hdrbuf)) { + DPRINT("hdr cnt %d", cnt); + return (EPROTO); + } + + /* + * Decode the header, get the length. + */ + hdr = ntohl(hdrbuf); + *mtype = (hdr >> 24) & 0xff; + *mlen = hdr & 0xffffff; + + if (mlen == 0) + return (0); + + /* + * Get a message buffer, read the payload + */ + if ((err = mb_init(mb, *mlen)) != 0) + return (err); + buf = mb->mb_top->m_data; + len = *mlen; + while (len > 0) { + if (!moreflag) { + if ((err = nb_ssn_pollin(ctx, tmo)) != 0) { + DPRINT("pollin err %d", err); + return (err); + } + } + + moreflag = 0; + cnt = t_rcv(fd, buf, len, &moreflag); + if (cnt < 0) { + err = get_xti_err(fd); + DPRINT("t_errno %d err %d", t_errno, err); + return (err); + } + buf += cnt; + len -= cnt; + } + mb->mb_top->m_len = *mlen; + mb->mb_count = *mlen; + + return (0); +} + +int +get_xti_err(int fd) +{ + int look; + if (t_errno == TSYSERR) + return (errno); + + if (t_errno == TLOOK) { + look = t_look(fd); + switch (look) { + case T_DISCONNECT: + (void) t_rcvdis(fd, NULL); + (void) t_snddis(fd, NULL); + return (ECONNRESET); + case T_ORDREL: + /* Received orderly release indication */ + (void) t_rcvrel(fd); + /* Send orderly release indicator */ + (void) t_sndrel(fd); + return (ECONNRESET); + } + } + return (EPROTO); +} + +/* + * Wait for data we can receive. + * Timeout is mSec., as for poll(2) + */ +static int +nb_ssn_pollin(struct smb_ctx *ctx, int tmo) +{ + struct pollfd pfd[1]; + int cnt, err; + + pfd[0].fd = ctx->ct_tran_fd; + pfd[0].events = POLLIN | POLLPRI; + pfd[0].revents = 0; + cnt = poll(pfd, 1, tmo); + switch (cnt) { + case 0: + err = ETIME; + break; + case -1: + err = errno; + break; + default: + err = 0; + break; + } + return (err); +} + +/* + * Send a NetBIOS session request and + * wait for the response. + */ +int +nb_ssn_request(struct smb_ctx *ctx, char *srvname) +{ + struct mbdata req, res; + struct nb_name lcl, srv; + int err, mtype, mlen; + char *ucwks; + + bzero(&req, sizeof (req)); + bzero(&res, sizeof (res)); + + if ((err = mb_init(&req, M_MINSIZE)) != 0) + goto errout; + + ucwks = utf8_str_toupper(ctx->ct_locname); + if (ucwks == NULL) { + err = ENOMEM; + goto errout; + } + + /* Local NB name. */ + snprintf(lcl.nn_name, NB_NAMELEN, "%-15.15s", ucwks); + lcl.nn_type = NBT_WKSTA; + lcl.nn_scope = ctx->ct_nb->nb_scope; + + /* Server NB name */ + snprintf(srv.nn_name, NB_NAMELEN, "%-15.15s", srvname); + srv.nn_type = NBT_SERVER; + srv.nn_scope = ctx->ct_nb->nb_scope; + + /* + * Build the request. Header is prepended later. + */ + if ((err = nb_name_encode(&req, &srv)) != 0) + goto errout; + if ((err = nb_name_encode(&req, &lcl)) != 0) + goto errout; + + /* + * Send it, wait for the reply. + */ + err = nb_ssn_send(ctx, &req, NB_SSN_REQUEST, req.mb_count); + if (err) { + DPRINT("send, err %d", err); + goto errout; + } + err = nb_ssn_recv(ctx, &res, &mtype, &mlen); + if (err) { + DPRINT("recv, err %d", err); + goto errout; + } + + if (mtype != NB_SSN_POSRESP) { + DPRINT("recv, mtype 0x%x", mtype); + err = ECONNREFUSED; + goto errout; + } + + return (0); + +errout: + mb_done(&res); + mb_done(&req); + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/nbns_rq.c b/usr/src/lib/libsmbfs/smb/nbns_rq.c index 773466b7fa..d0e85209e7 100644 --- a/usr/src/lib/libsmbfs/smb/nbns_rq.c +++ b/usr/src/lib/libsmbfs/smb/nbns_rq.c @@ -55,6 +55,7 @@ #include <netsmb/nb_lib.h> #include <netsmb/mchain.h> +#include "charsets.h" #include "private.h" /* @@ -96,7 +97,80 @@ static int nbns_rq_getrr(struct nbns_rq *rqp, struct nbns_rr *rrp); static int nbns_rq_prepare(struct nbns_rq *rqp); static int nbns_rq(struct nbns_rq *rqp); -static struct nb_ifdesc *nb_iflist = NULL; +/* + * Call NetBIOS name lookup and return a result in the + * same form as getaddrinfo(3) returns. Return code is + * zero or one of the EAI_xxx codes like getaddrinfo. + */ +int +nbns_getaddrinfo(const char *name, struct nb_ctx *nbc, struct addrinfo **res) +{ + struct addrinfo *nai = NULL; + struct sockaddr *sap = NULL; + char *ucname = NULL; + int err; + + /* + * Try NetBIOS name lookup. + */ + if (strlen(name) >= NB_NAMELEN) { + err = EAI_OVERFLOW; + goto out; + } + ucname = utf8_str_toupper(name); + if (ucname == NULL) + goto nomem; + + /* Note: this returns an NBERROR value. */ + err = nbns_resolvename(ucname, nbc, &sap); + if (err) { + if (smb_verbose) + smb_error(dgettext(TEXT_DOMAIN, + "nbns_resolvename: %s"), + err, name); + err = EAI_NODATA; + goto out; + } + /* Note: sap allocated */ + + /* + * Build the addrinfo struct to return. + */ + nai = malloc(sizeof (*nai)); + if (nai == NULL) + goto nomem; + bzero(nai, sizeof (*nai)); + + nai->ai_flags = AI_CANONNAME; + nai->ai_family = sap->sa_family; + nai->ai_socktype = SOCK_STREAM; + nai->ai_canonname = ucname; + ucname = NULL; + + /* + * The type of this is really sockaddr_in, + * but is returned in the generic form. + * See nbns_resolvename. + */ + nai->ai_addrlen = sizeof (struct sockaddr_in); + nai->ai_addr = sap; + + *res = nai; + return (0); + +nomem: + err = EAI_MEMORY; +out: + if (nai != NULL) + free(nai); + if (sap) + free(sap); + if (ucname) + free(ucname); + *res = NULL; + + return (err); +} int nbns_resolvename(const char *name, struct nb_ctx *ctx, struct sockaddr **adpp) @@ -107,7 +181,7 @@ nbns_resolvename(const char *name, struct nb_ctx *ctx, struct sockaddr **adpp) struct sockaddr_in *dest; int error, rdrcount, len; - if (strlen(name) > NB_NAMELEN) + if (strlen(name) >= NB_NAMELEN) return (NBERROR(NBERR_NAMETOOLONG)); error = nbns_rq_create(NBNS_OPCODE_QUERY, ctx, &rqp); if (error) @@ -169,12 +243,11 @@ nbns_resolvename(const char *name, struct nb_ctx *ctx, struct sockaddr **adpp) return (ENOMEM); bzero(dest, len); /* - * Solaris sockaddr_in doesn't have this field. + * Solaris sockaddr_in doesn't a sin_len field. * dest->sin_len = len; */ - dest->sin_family = AF_INET; + dest->sin_family = AF_NETBIOS; /* nb_lib.h */ bcopy(rr.rr_data + 2, &dest->sin_addr.s_addr, 4); - dest->sin_port = htons(SMB_TCP_PORT); *adpp = (struct sockaddr *)dest; ctx->nb_lastns = rqp->nr_sender; break; @@ -183,20 +256,12 @@ nbns_resolvename(const char *name, struct nb_ctx *ctx, struct sockaddr **adpp) return (error); } -static char * -smb_optstrncpy(char *d, char *s, unsigned maxlen) -{ - if (d && s) { - strncpy(d, s, maxlen); - d[maxlen] = (char)0; - } - return (d); -} - - +/* + * NB: system, workgroup are both NB_NAMELEN + */ int -nbns_getnodestatus(struct sockaddr *targethost, - struct nb_ctx *ctx, char *system, char *workgroup) +nbns_getnodestatus(struct nb_ctx *ctx, + struct in_addr *targethost, char *system, char *workgroup) { struct nbns_rq *rqp; struct nbns_rr rr; @@ -204,12 +269,9 @@ nbns_getnodestatus(struct sockaddr *targethost, struct nbns_nr *nrp; char nrtype; char *cp, *retname = NULL; - struct sockaddr_in *dest; unsigned char nrcount; - int error, rdrcount, i, foundserver = 0, foundgroup = 0; + int error, i, foundserver = 0, foundgroup = 0; - if (targethost->sa_family != AF_INET) - return (EINVAL); error = nbns_rq_create(NBNS_OPCODE_QUERY, ctx, &rqp); if (error) return (error); @@ -224,10 +286,7 @@ nbns_getnodestatus(struct sockaddr *targethost, rqp->nr_qdcount = 1; rqp->nr_maxretry = 2; - /* LINTED */ - dest = (struct sockaddr_in *)targethost; - rqp->nr_dest = dest->sin_addr; - + rqp->nr_dest = *targethost; error = nbns_rq_prepare(rqp); if (error) { nbns_rq_done(rqp); @@ -266,12 +325,14 @@ nbns_getnodestatus(struct sockaddr *targethost, *cp = (char)0; } nrp->ns_flags = ntohs(nrp->ns_flags); + DPRINT(" %s[%02x] Flags 0x%x", + nrp->ns_name, nrtype, nrp->ns_flags); if (nrp->ns_flags & NBNS_GROUPFLG) { if (!foundgroup || (foundgroup != NBT_WKSTA+1 && nrtype == NBT_WKSTA)) { - smb_optstrncpy(workgroup, nrp->ns_name, - SMB_MAXUSERNAMELEN); + strlcpy(workgroup, nrp->ns_name, + NB_NAMELEN); foundgroup = nrtype+1; } } else { @@ -281,14 +342,17 @@ nbns_getnodestatus(struct sockaddr *targethost, */ retname = nrp->ns_name; } - if (nrtype == NBT_SERVER) { - smb_optstrncpy(system, nrp->ns_name, - SMB_MAXSRVNAMELEN); + /* + * Keep the first NBT_SERVER name. + */ + if (nrtype == NBT_SERVER && foundserver == 0) { + strlcpy(system, nrp->ns_name, + NB_NAMELEN); foundserver = 1; } } if (!foundserver) - smb_optstrncpy(system, retname, SMB_MAXSRVNAMELEN); + strlcpy(system, retname, NB_NAMELEN); ctx->nb_lastns = rqp->nr_sender; out: @@ -370,8 +434,7 @@ nbns_rq_prepare(struct nbns_rq *rqp) struct nb_ctx *ctx = rqp->nr_nbd; struct mbdata *mbp = &rqp->nr_rq; uint16_t ofr; /* opcode, flags, rcode */ - uchar_t *cp; - int len, error; + int error; /* * Replacing with one argument. @@ -396,11 +459,7 @@ nbns_rq_prepare(struct nbns_rq *rqp) if (rqp->nr_qdcount) { if (rqp->nr_qdcount > 1) return (EINVAL); - len = nb_name_len(rqp->nr_qdname); - error = mb_fit(mbp, len, (char **)&cp); - if (error) - return (error); - nb_name_encode(rqp->nr_qdname, cp); + nb_name_encode(mbp, rqp->nr_qdname); mb_put_uint16be(mbp, rqp->nr_qdtype); mb_put_uint16be(mbp, rqp->nr_qdclass); } @@ -485,7 +544,7 @@ nbns_rq_send(struct nbns_rq *rqp, in_addr_t ina) bzero(&dest, sizeof (dest)); dest.sin_family = AF_INET; - dest.sin_port = htons(NBNS_UDP_PORT); + dest.sin_port = htons(IPPORT_NETBIOS_NS); dest.sin_addr.s_addr = ina; if (ina == INADDR_BROADCAST) { @@ -520,7 +579,6 @@ nbns_rq(struct nbns_rq *rqp) struct nb_ctx *ctx = rqp->nr_nbd; struct mbdata *mbp = &rqp->nr_rq; uint16_t ofr, rpid; - uint8_t nmflags; int error, tries, maxretry; error = nbns_rq_opensocket(rqp); @@ -599,6 +657,8 @@ do_recv: return (NBERROR(NBERR_INVALIDRESPONSE)); break; } + if (tries == maxretry) + return (NBERROR(NBERR_HOSTNOTFOUND)); mb_get_uint16be(mbp, &ofr); rqp->nr_rpnmflags = (ofr >> 4) & 0x7F; diff --git a/usr/src/lib/libsmbfs/smb/negprot.c b/usr/src/lib/libsmbfs/smb/negprot.c new file mode 100644 index 0000000000..6c1649f1bb --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/negprot.c @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2000-2001 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. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * SMB Negotiate Protocol, and related. + * Copied from the driver: smb_smb.c + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +/* + * SMB dialects that we know about. + */ +struct smb_dialect { + int d_id; + const char *d_name; +}; +static struct smb_dialect smb_dialects[] = { + {SMB_DIALECT_CORE, "PC NETWORK PROGRAM 1.0"}, + {SMB_DIALECT_LANMAN1_0, "LANMAN1.0"}, + {SMB_DIALECT_LANMAN2_0, "LM1.2X002"}, + {SMB_DIALECT_LANMAN2_1, "LANMAN2.1"}, + {SMB_DIALECT_NTLM0_12, "NT LM 0.12"}, + {-1, NULL} +}; + +#define SMB_DIALECT_MAX \ + (sizeof (smb_dialects) / sizeof (struct smb_dialect) - 2) + +/* + * SMB Negotiate Protocol + * Based on code from the driver: smb_smb.c + * + * If using Extended Security, oblob (output) + * will hold the initial security "hint". + */ +int +smb_negprot(struct smb_ctx *ctx, struct mbdata *oblob) +{ + struct smb_sopt *sv = &ctx->ct_sopt; + struct smb_iods *is = &ctx->ct_iods; + struct smb_rq *rqp; + struct mbdata *mbp; + struct smb_dialect *dp; + int err, len; + uint8_t wc, stime[8], eklen; + uint16_t dindex, bc; + int will_sign = 0; + + /* + * Initialize: vc_hflags and vc_hflags2. + * Note: ctx->ct_hflags* are copied into the + * (per request) rqp->rq_hflags* by smb_rq_init, + * so changing them after that call will not + * affect THIS request. + */ + ctx->ct_hflags = SMB_FLAGS_CASELESS; + ctx->ct_hflags2 = (SMB_FLAGS2_ERR_STATUS | + SMB_FLAGS2_KNOWS_LONG_NAMES); + + /* + * Sould we offer extended security? + * We'll turn this back off below if + * the server doesn't support it. + */ + if (ctx->ct_vopt & SMBVOPT_EXT_SEC) + ctx->ct_hflags2 |= SMB_FLAGS2_EXT_SEC; + + /* + * The initial UID needs to be zero, + * or Windows XP says "bad user". + * The initial TID is all ones, but + * we don't use it or store it here + * because the driver handles that. + */ + is->is_smbuid = 0; + + /* + * In case we're reconnecting, + * free previous stuff. + */ + ctx->ct_mac_seqno = 0; + if (ctx->ct_mackey != NULL) { + free(ctx->ct_mackey); + ctx->ct_mackey = NULL; + ctx->ct_mackeylen = 0; + } + + sv = &ctx->ct_sopt; + bzero(sv, sizeof (struct smb_sopt)); + + err = smb_rq_init(ctx, SMB_COM_NEGOTIATE, &rqp); + if (err) + return (err); + + /* + * Build the SMB request. + */ + mbp = &rqp->rq_rq; + mb_put_uint8(mbp, 0); /* word count */ + smb_rq_bstart(rqp); + for (dp = smb_dialects; dp->d_id != -1; dp++) { + mb_put_uint8(mbp, SMB_DT_DIALECT); + mb_put_astring(mbp, dp->d_name); + } + smb_rq_bend(rqp); + + /* + * This does the OTW call + */ + err = smb_rq_internal(ctx, rqp); + if (err) { + DPRINT("call failed, err %d", err); + goto errout; + } + if (rqp->rq_status != 0) { + DPRINT("nt status 0x%x", rqp->rq_status); + err = EBADRPC; + goto errout; + } + + /* + * Decode the response + * + * Comments to right show names as described in + * The Microsoft SMB Protocol spec. [MS-SMB] + * section 2.2.3 + */ + mbp = &rqp->rq_rp; + (void) mb_get_uint8(mbp, &wc); + err = mb_get_uint16le(mbp, &dindex); + if (err || dindex > SMB_DIALECT_MAX) { + DPRINT("err %d dindex %d", err, (int)dindex); + goto errout; + } + dp = smb_dialects + dindex; + sv->sv_proto = dp->d_id; + DPRINT("Dialect %s", dp->d_name); + if (dp->d_id < SMB_DIALECT_NTLM0_12) { + /* XXX: User-visible warning too? */ + DPRINT("old dialect %s", dp->d_name); + goto errout; + } + if (wc != 17) { + DPRINT("bad wc %d", (int)wc); + goto errout; + } + mb_get_uint8(mbp, &sv->sv_sm); /* SecurityMode */ + mb_get_uint16le(mbp, &sv->sv_maxmux); /* MaxMpxCount */ + mb_get_uint16le(mbp, &sv->sv_maxvcs); /* MaxCountVCs */ + mb_get_uint32le(mbp, &sv->sv_maxtx); /* MaxBufferSize */ + mb_get_uint32le(mbp, &sv->sv_maxraw); /* MaxRawSize */ + mb_get_uint32le(mbp, &sv->sv_skey); /* SessionKey */ + mb_get_uint32le(mbp, &sv->sv_caps); /* Capabilities */ + mb_get_mem(mbp, (char *)stime, 8); /* SystemTime(s) */ + mb_get_uint16le(mbp, (uint16_t *)&sv->sv_tz); + mb_get_uint8(mbp, &eklen); /* EncryptionKeyLength */ + err = mb_get_uint16le(mbp, &bc); /* ByteCount */ + if (err) + goto errout; + + /* BEGIN CSTYLED */ + /* + * Will we do SMB signing? Or block the connection? + * The table below describes this logic. References: + * [Windows Server Protocols: MS-SMB, sec. 3.2.4.2.3] + * http://msdn.microsoft.com/en-us/library/cc212511.aspx + * http://msdn.microsoft.com/en-us/library/cc212929.aspx + * + * Srv/Cli | Required | Enabled | If Required | Disabled + * ------------+----------+------------+-------------+----------- + * Required | Signed | Signed | Signed | Blocked [1] + * ------------+----------+------------+-------------+----------- + * Enabled | Signed | Signed | Not Signed | Not Signed + * ------------+----------+------------+-------------+----------- + * If Required | Signed | Not Signed | Not Signed | Not Signed + * ------------+----------+------------+-------------+----------- + * Disabled | Blocked | Not Signed | Not Signed | Not Signed + * + * [1] Like Windows 2003 and later, we don't really implement + * the "Disabled" setting. Instead we implement "If Required", + * so we always sign if the server requires signing. + */ + /* END CSTYLED */ + + if (sv->sv_sm & SMB_SM_SIGS_REQUIRE) { + /* + * Server requires signing. We will sign, + * even if local setting is "disabled". + */ + will_sign = 1; + } else if (sv->sv_sm & SMB_SM_SIGS) { + /* + * Server enables signing (client's option). + * If enabled locally, do signing. + */ + if (ctx->ct_vopt & SMBVOPT_SIGNING_ENABLED) + will_sign = 1; + /* else not signing. */ + } else { + /* + * Server does not support signing. + * If we "require" it, bail now. + */ + if (ctx->ct_vopt & SMBVOPT_SIGNING_REQUIRED) { + DPRINT("Client requires signing " + "but server has it disabled."); + err = EBADRPC; + goto errout; + } + } + + if (will_sign) { + ctx->ct_vcflags |= SMBV_WILL_SIGN; + } + DPRINT("Security signatures: %d", will_sign); + + if (sv->sv_caps & SMB_CAP_UNICODE) { + ctx->ct_vcflags |= SMBV_UNICODE; + ctx->ct_hflags2 |= SMB_FLAGS2_UNICODE; + + } + if ((sv->sv_caps & SMB_CAP_STATUS32) == 0) { + /* + * They don't do NT error codes. + * + * If we send requests with + * SMB_FLAGS2_ERR_STATUS set in + * Flags2, Windows 98, at least, + * appears to send replies with that + * bit set even though it sends back + * DOS error codes. (They probably + * just use the request header as + * a template for the reply header, + * and don't bother clearing that bit.) + * + * Therefore, we clear that bit in + * our vc_hflags2 field. + */ + ctx->ct_hflags2 &= ~SMB_FLAGS2_ERR_STATUS; + } + if (dp->d_id == SMB_DIALECT_NTLM0_12 && + sv->sv_maxtx < 4096 && + (sv->sv_caps & SMB_CAP_NT_SMBS) == 0) { + ctx->ct_vcflags |= SMBV_WIN95; + DPRINT("Win95 detected"); + } + + /* + * The rest of the message varies depending on + * whether we've negotiated "extended security". + * + * With extended security, we have: + * Server_GUID (length 16) + * Security_BLOB + * Otherwise we have: + * EncryptionKey (length is eklen) + * PrimaryDomain + */ + if (sv->sv_caps & SMB_CAP_EXT_SECURITY) { + struct mbuf *m; + DPRINT("Ext.Security: yes"); + + /* + * Skip the server GUID. + */ + err = mb_get_mem(mbp, NULL, SMB_GUIDLEN); + if (err) + goto errout; + /* + * Remainder is the security blob. + * Note: eklen "must be ignored" [MS-SMB] + */ + len = (int)bc - SMB_GUIDLEN; + if (len < 0) + goto errout; + + /* + * Get the (optional) SPNEGO "hint". + */ + err = mb_get_mbuf(mbp, len, &m); + if (err) + goto errout; + mb_initm(oblob, m); + oblob->mb_count = len; + } else { + DPRINT("Ext.Security: no"); + ctx->ct_hflags2 &= ~SMB_FLAGS2_EXT_SEC; + + /* + * Save the "Encryption Key" (the challenge). + * + * Sanity check: make sure the sec. blob length + * isn't bigger than the byte count. + */ + if (bc < eklen || eklen < NTLM_CHAL_SZ) { + err = EBADRPC; + goto errout; + } + err = mb_get_mem(mbp, (char *)ctx->ct_ntlm_chal, NTLM_CHAL_SZ); + /* + * Server domain follows (ignored) + * Note: NOT aligned(2) - unusual! + */ + } + + smb_rq_done(rqp); + + /* + * A few sanity checks on what we received, + * becuse we will send these in ssnsetup. + * + * Maximum outstanding requests (we care), + * and Max. VCs (we only use one). Also, + * MaxBufferSize lower limit per spec. + */ + if (sv->sv_maxmux < 1) + sv->sv_maxmux = 1; + if (sv->sv_maxvcs < 1) + sv->sv_maxvcs = 1; + if (sv->sv_maxtx < 1024) + sv->sv_maxtx = 1024; + + /* + * Maximum transfer size. + * Sanity checks: + * + * Let's be conservative about an upper limit here. + * Win2k uses 16644 (and others) so 32k should be a + * reasonable sanity limit for this value. + * + * Note that this limit does NOT affect READX/WRITEX + * with CAP_LARGE_..., which we nearly always use. + */ + is->is_txmax = sv->sv_maxtx; + if (is->is_txmax > 0x8000) + is->is_txmax = 0x8000; + + /* + * Max read/write sizes, WITHOUT overhead. + * This is just the payload size, so we must + * leave room for the SMB headers, etc. + * This is just the ct_txmax value, but + * reduced and rounded down. Tricky bit: + * + * Servers typically give us a value that's + * some nice "round" number, i.e 0x4000 plus + * some overhead, i.e. Win2k: 16644==0x4104 + * Subtract for the SMB header (32) and the + * SMB command word and byte vectors (34?), + * then round down to a 512 byte multiple. + */ + len = is->is_txmax - 68; + len &= 0xFE00; + /* XXX: Not sure yet which of these to keep. */ + is->is_rwmax = len; + is->is_rxmax = len; + is->is_wxmax = len; + + return (0); + +errout: + smb_rq_done(rqp); + if (err == 0) + err = EBADRPC; + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/netshareenum.c b/usr/src/lib/libsmbfs/smb/netshareenum.c index 2d7f9c2c3a..2ee1fd7792 100644 --- a/usr/src/lib/libsmbfs/smb/netshareenum.c +++ b/usr/src/lib/libsmbfs/smb/netshareenum.c @@ -37,7 +37,7 @@ /* END CSTYLED */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -47,6 +47,7 @@ #include <errno.h> #include <netsmb/mchain.h> +#include <netsmb/smb.h> #include <netsmb/smb_lib.h> #include <netsmb/smb_rap.h> #include <netsmb/smb_netshareenum.h> @@ -275,8 +276,6 @@ smb_rap_NetShareEnum(struct smb_ctx *ctx, int sLevel, void *pbBuffer, struct smb_rap *rap; long lval = -1; int error; - char *pass; - int i; error = smb_rap_create(0, "WrLeh", "B13BWz", &rap); if (error) @@ -375,5 +374,6 @@ smb_netshareenum(struct smb_ctx *ctx, int *entriesp, int *totalp, * XXX - do so only if it failed because we couldn't open * the pipe? */ - return (rap_netshareenum(ctx, entriesp, totalp, entry_listp)); + error = rap_netshareenum(ctx, entriesp, totalp, entry_listp); + return (error); } diff --git a/usr/src/lib/libsmbfs/smb/newvc.c b/usr/src/lib/libsmbfs/smb/newvc.c new file mode 100644 index 0000000000..0153ffd437 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/newvc.c @@ -0,0 +1,118 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Create a new VC given a list of addresses. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/netbios.h> +#include <netsmb/nb_lib.h> +#include <netsmb/smb_dev.h> + +#include "charsets.h" +#include "private.h" + +/* + * Ask the IOD to create a VC with this IP address. + */ +static int +newvc(struct smb_ctx *ctx, struct addrinfo *ai) +{ + smbioc_ossn_t *ssn = &ctx->ct_ssn; + + /* + * Copy the passed address into ssn_srvaddr, + * but first sanity-check lengths. Also, + * zero it first to avoid trailing junk. + */ + if (ai->ai_addrlen > sizeof (ssn->ssn_srvaddr)) + return (EINVAL); + bzero(&ssn->ssn_srvaddr, sizeof (ssn->ssn_srvaddr)); + bcopy(ai->ai_addr, &ssn->ssn_srvaddr, ai->ai_addrlen); + + return (smb_iod_cl_newvc(ctx)); +} + +/* + * Setup a new VC via the IOD. + * Similar to findvc.c + */ +int +smb_ctx_newvc(struct smb_ctx *ctx) +{ + struct addrinfo *ai; + int err = ENOENT; + + /* Should already have the address list. */ + if ((ctx->ct_flags & SMBCF_RESOLVED) == 0) + return (EINVAL); + + for (ai = ctx->ct_addrinfo; ai; ai = ai->ai_next) { + + switch (ai->ai_family) { + + case AF_INET: + case AF_INET6: + case AF_NETBIOS: + err = newvc(ctx, ai); + break; + + default: + DPRINT("skipped family %d", ai->ai_family); + break; + } + + if (err == 0) { + ctx->ct_flags |= SMBCF_SSNACTIVE; + return (0); + } + } + + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/nls.c b/usr/src/lib/libsmbfs/smb/nls.c index 03fe1bec13..e5b6719716 100644 --- a/usr/src/lib/libsmbfs/smb/nls.c +++ b/usr/src/lib/libsmbfs/smb/nls.c @@ -32,8 +32,6 @@ * $Id: nls.c,v 1.10 2004/12/13 00:25:22 lindak Exp $ */ -#pragma ident "%Z%%M% %I% %E% SMI" - #include <sys/types.h> #include <ctype.h> #include <errno.h> @@ -52,8 +50,8 @@ typedef void *iconv_t; static size_t(*my_iconv)(iconv_t, const char **, size_t *, char **, size_t *); -u_char nls_lower[256]; -u_char nls_upper[256]; +uchar_t nls_lower[256]; +uchar_t nls_upper[256]; static iconv_t nls_toext, nls_toloc; static int iconv_loaded; @@ -71,13 +69,14 @@ nls_setlocale(const char *name) nls_lower[i] = tolower(i); nls_upper[i] = toupper(i); } - return 0; + return (0); } +/*ARGSUSED*/ int nls_setrecode(const char *local, const char *external) { - return ENOENT; + return (ENOENT); } char * @@ -87,15 +86,15 @@ nls_str_toloc(char *dst, const char *src) size_t inlen, outlen; if (!iconv_loaded) - return strcpy(dst, src); + return (strcpy(dst, src)); if (nls_toloc == (iconv_t)0) - return strcpy(dst, src); + return (strcpy(dst, src)); inlen = outlen = strlen(src); my_iconv(nls_toloc, NULL, NULL, &p, &outlen); my_iconv(nls_toloc, &src, &inlen, &p, &outlen); *p = 0; - return dst; + return (dst); } char * @@ -105,15 +104,15 @@ nls_str_toext(char *dst, const char *src) size_t inlen, outlen; if (!iconv_loaded) - return strcpy(dst, src); + return (strcpy(dst, src)); if (nls_toext == (iconv_t)0) - return strcpy(dst, src); + return (strcpy(dst, src)); inlen = outlen = strlen(src); my_iconv(nls_toext, NULL, NULL, &p, &outlen); my_iconv(nls_toext, &src, &inlen, &p, &outlen); *p = 0; - return dst; + return (dst); } void * @@ -124,17 +123,17 @@ nls_mem_toloc(void *dst, const void *src, int size) size_t inlen, outlen; if (!iconv_loaded) - return memcpy(dst, src, size); + return (memcpy(dst, src, size)); if (size == 0) - return NULL; + return (NULL); if (nls_toloc == (iconv_t)0) - return memcpy(dst, src, size); + return (memcpy(dst, src, size)); inlen = outlen = size; my_iconv(nls_toloc, NULL, NULL, &p, &outlen); my_iconv(nls_toloc, &s, &inlen, &p, &outlen); - return dst; + return (dst); } void * @@ -145,15 +144,15 @@ nls_mem_toext(void *dst, const void *src, int size) size_t inlen, outlen; if (size == 0) - return NULL; + return (NULL); if (!iconv_loaded || nls_toext == (iconv_t)0) - return memcpy(dst, src, size); + return (memcpy(dst, src, size)); inlen = outlen = size; my_iconv(nls_toext, NULL, NULL, &p, &outlen); my_iconv(nls_toext, &s, &inlen, &p, &outlen); - return dst; + return (dst); } char * @@ -164,7 +163,7 @@ nls_str_upper(char *dst, const char *src) while (*src) *dst++ = toupper(*src++); *dst = 0; - return p; + return (p); } char * @@ -175,5 +174,5 @@ nls_str_lower(char *dst, const char *src) while (*src) *dst++ = tolower(*src++); *dst = 0; - return p; + return (p); } diff --git a/usr/src/lib/libsmbfs/smb/ntlm.c b/usr/src/lib/libsmbfs/smb/ntlm.c new file mode 100644 index 0000000000..8119e62b65 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ntlm.c @@ -0,0 +1,584 @@ +/* + * Copyright (c) 2000-2001, 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: smb_crypt.c,v 1.13 2005/01/26 23:50:50 lindak Exp $ + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * NTLM support functions + * + * Some code from the driver: smb_smb.c, smb_crypt.c + */ + +#include <sys/errno.h> +#include <sys/types.h> +#include <sys/md4.h> +#include <sys/md5.h> + +#include <ctype.h> +#include <stdlib.h> +#include <strings.h> + +#include <netsmb/smb_lib.h> + +#include "private.h" +#include "charsets.h" +#include "smb_crypt.h" +#include "ntlm.h" + + +/* + * ntlm_compute_lm_hash + * + * Compute an LM hash given a password + * + * Output: + * hash: 16-byte "LanMan" (LM) hash. + * Inputs: + * ucpw: User's password, upper-case UTF-8 string. + * + * Source: Implementing CIFS (Chris Hertel) + * + * P14 = UCPW padded to 14-bytes, or truncated (as needed) + * result = Encrypt(Key=P14, Data=MagicString) + */ +int +ntlm_compute_lm_hash(uchar_t *hash, const char *pass) +{ + static const uchar_t M8[8] = "KGS!@#$%"; + uchar_t P14[14 + 1]; + int err; + char *ucpw; + + /* First, convert the p/w to upper case. */ + ucpw = utf8_str_toupper(pass); + if (ucpw == NULL) + return (ENOMEM); + + /* Pad or truncate the upper-case P/W as needed. */ + bzero(P14, sizeof (P14)); + (void) strncpy((char *)P14, ucpw, 14); + + /* Compute the hash. */ + err = smb_encrypt_DES(hash, NTLM_HASH_SZ, + P14, 14, M8, 8); + + free(ucpw); + return (err); +} + +/* + * ntlm_compute_nt_hash + * + * Compute an NT hash given a password in UTF-8. + * + * Output: + * hash: 16-byte "NT" hash. + * Inputs: + * upw: User's password, mixed-case UCS-2LE. + * pwlen: Size (in bytes) of upw + */ +int +ntlm_compute_nt_hash(uchar_t *hash, const char *pass) +{ + MD4_CTX ctx; + uint16_t *unipw = NULL; + int pwsz; + + /* First, convert the password to unicode. */ + unipw = convert_utf8_to_leunicode(pass); + if (unipw == NULL) + return (ENOMEM); + pwsz = unicode_strlen(unipw) << 1; + + /* Compute the hash. */ + MD4Init(&ctx); + MD4Update(&ctx, unipw, pwsz); + MD4Final(hash, &ctx); + + free(unipw); + return (0); +} + +/* + * ntlm_v1_response + * + * Create an LM response from the given LM hash and challenge, + * or an NTLM repsonse from a given NTLM hash and challenge. + * Both response types are 24 bytes (NTLM_V1_RESP_SZ) + */ +static int +ntlm_v1_response(uchar_t *resp, + const uchar_t *hash, + const uchar_t *chal, int clen) +{ + uchar_t S21[21]; + int err; + + /* + * 14-byte LM Hash should be padded with 5 nul bytes to create + * a 21-byte string to be used in producing LM response + */ + bzero(&S21, sizeof (S21)); + bcopy(hash, S21, NTLM_HASH_SZ); + + /* padded LM Hash -> LM Response */ + err = smb_encrypt_DES(resp, NTLM_V1_RESP_SZ, + S21, 21, chal, clen); + return (err); +} + +/* + * Calculate an NTLMv1 session key (16 bytes). + */ +static void +ntlm_v1_session_key(uchar_t *ssn_key, const uchar_t *nt_hash) +{ + MD4_CTX md4; + + MD4Init(&md4); + MD4Update(&md4, nt_hash, NTLM_HASH_SZ); + MD4Final(ssn_key, &md4); +} + +/* + * Compute both the LM(v1) response and the NTLM(v1) response, + * and put them in the mbdata chains passed. This allocates + * mbuf chains in the output args, which the caller frees. + */ +int +ntlm_put_v1_responses(struct smb_ctx *ctx, + struct mbdata *lm_mbp, struct mbdata *nt_mbp) +{ + uchar_t *lmresp, *ntresp; + int err; + + /* Get mbuf chain for the LM response. */ + if ((err = mb_init(lm_mbp, NTLM_V1_RESP_SZ)) != 0) + return (err); + + /* Get mbuf chain for the NT response. */ + if ((err = mb_init(nt_mbp, NTLM_V1_RESP_SZ)) != 0) + return (err); + + /* + * Compute the LM response, derived + * from the challenge and the ASCII + * password (if authflags allow). + */ + mb_fit(lm_mbp, NTLM_V1_RESP_SZ, (char **)&lmresp); + bzero(lmresp, NTLM_V1_RESP_SZ); + if (ctx->ct_authflags & SMB_AT_LM1) { + /* They asked to send the LM hash too. */ + err = ntlm_v1_response(lmresp, ctx->ct_lmhash, + ctx->ct_ntlm_chal, NTLM_CHAL_SZ); + if (err) + return (err); + } + + /* + * Compute the NTLM response, derived from + * the challenge and the NT hash. + */ + mb_fit(nt_mbp, NTLM_V1_RESP_SZ, (char **)&ntresp); + bzero(ntresp, NTLM_V1_RESP_SZ); + err = ntlm_v1_response(ntresp, ctx->ct_nthash, + ctx->ct_ntlm_chal, NTLM_CHAL_SZ); + + /* + * Compute the session key + */ + ntlm_v1_session_key(ctx->ct_ssn_key, ctx->ct_nthash); + + return (err); +} + +/* + * A variation on HMAC-MD5 known as HMACT64 is used by Windows systems. + * The HMACT64() function is the same as the HMAC-MD5() except that + * it truncates the input key to 64 bytes rather than hashing it down + * to 16 bytes using the MD5() function. + * + * Output: digest (16-bytes) + */ +static void +HMACT64(uchar_t *digest, + const uchar_t *key, size_t key_len, + const uchar_t *data, size_t data_len) +{ + MD5_CTX context; + uchar_t k_ipad[64]; /* inner padding - key XORd with ipad */ + uchar_t k_opad[64]; /* outer padding - key XORd with opad */ + int i; + + /* if key is longer than 64 bytes use only the first 64 bytes */ + if (key_len > 64) + key_len = 64; + + /* + * The HMAC-MD5 (and HMACT64) transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, data)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and data is the data being protected. + */ + + /* start out by storing key in pads */ + bzero(k_ipad, sizeof (k_ipad)); + bzero(k_opad, sizeof (k_opad)); + bcopy(key, k_ipad, key_len); + bcopy(key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* + * perform inner MD5 + */ + MD5Init(&context); /* init context for 1st pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, data, data_len); /* then data of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + + /* + * perform outer MD5 + */ + MD5Init(&context); /* init context for 2nd pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ +} + + +/* + * Compute an NTLMv2 hash given the NTLMv1 hash, the user name, + * and the destination (machine or domain name). + * + * Output: + * v2hash: 16-byte NTLMv2 hash. + * Inputs: + * v1hash: 16-byte NTLMv1 hash. + * user: User name, UPPER-case UTF-8 string. + * destination: Domain or server, MIXED-case UTF-8 string. + */ +static int +ntlm_v2_hash(uchar_t *v2hash, const uchar_t *v1hash, + const char *user, const char *destination) +{ + int ulen, dlen; + size_t ucs2len; + uint16_t *ucs2data = NULL; + char *utf8data = NULL; + int err = ENOMEM; + + /* + * v2hash = HMACT64(v1hash, 16, concat(upcase(user), dest)) + * where "dest" is the domain or server name ("target name") + * Note: user name is converted to upper-case by the caller. + */ + + /* utf8data = concat(user, dest) */ + ulen = strlen(user); + dlen = strlen(destination); + utf8data = malloc(ulen + dlen + 1); + if (utf8data == NULL) + goto out; + bcopy(user, utf8data, ulen); + bcopy(destination, utf8data + ulen, dlen + 1); + + /* Convert to UCS-2LE */ + ucs2data = convert_utf8_to_leunicode(utf8data); + if (ucs2data == NULL) + goto out; + ucs2len = 2 * unicode_strlen(ucs2data); + + HMACT64(v2hash, v1hash, NTLM_HASH_SZ, + (uchar_t *)ucs2data, ucs2len); + err = 0; +out: + if (ucs2data) + free(ucs2data); + if (utf8data) + free(utf8data); + return (err); +} + +/* + * Compute a partial LMv2 or NTLMv2 response (first 16-bytes). + * The full response is composed by the caller by + * appending the client_data to the returned hash. + * + * Output: + * rhash: _partial_ LMv2/NTLMv2 response (first 16-bytes) + * Inputs: + * v2hash: 16-byte NTLMv2 hash. + * C8: Challenge from server (8 bytes) + * client_data: client nonce (for LMv2) or the + * "blob" from ntlm_build_target_info (NTLMv2) + */ +static int +ntlm_v2_resp_hash(uchar_t *rhash, + const uchar_t *v2hash, const uchar_t *C8, + const uchar_t *client_data, size_t cdlen) +{ + size_t dlen; + uchar_t *data = NULL; + + /* data = concat(C8, client_data) */ + dlen = 8 + cdlen; + data = malloc(dlen); + if (data == NULL) + return (ENOMEM); + bcopy(C8, data, 8); + bcopy(client_data, data + 8, cdlen); + + HMACT64(rhash, v2hash, NTLM_HASH_SZ, data, dlen); + + free(data); + return (0); +} + +/* + * Calculate an NTLMv2 session key (16 bytes). + */ +static void +ntlm_v2_session_key(uchar_t *ssn_key, + const uchar_t *v2hash, + const uchar_t *ntresp) +{ + + /* session key uses only 1st 16 bytes of ntresp */ + HMACT64(ssn_key, v2hash, NTLM_HASH_SZ, ntresp, NTLM_HASH_SZ); +} + + +/* + * Compute both the LMv2 response and the NTLMv2 response, + * and put them in the mbdata chains passed. This allocates + * mbuf chains in the output args, which the caller frees. + * Also computes the session key. + */ +int +ntlm_put_v2_responses(struct smb_ctx *ctx, struct mbdata *ti_mbp, + struct mbdata *lm_mbp, struct mbdata *nt_mbp) +{ + uchar_t *lmresp, *ntresp; + int err; + char *ucdom = NULL; /* user's domain */ + char *ucuser = NULL; /* account name */ + uchar_t v2hash[NTLM_HASH_SZ]; + struct mbuf *tim = ti_mbp->mb_top; + + if ((err = mb_init(lm_mbp, M_MINSIZE)) != 0) + return (err); + if ((err = mb_init(nt_mbp, M_MINSIZE)) != 0) + return (err); + + /* + * Convert the user name to upper-case, as + * that's what's used when computing LMv2 + * and NTLMv2 responses. Also the domain. + */ + ucdom = utf8_str_toupper(ctx->ct_domain); + ucuser = utf8_str_toupper(ctx->ct_user); + if (ucdom == NULL || ucuser == NULL) { + err = ENOMEM; + goto out; + } + + /* + * Compute the NTLMv2 hash (see above) + * Needs upper-case user, domain. + */ + err = ntlm_v2_hash(v2hash, ctx->ct_nthash, ucuser, ucdom); + if (err) + goto out; + + /* + * Compute the LMv2 response, derived from + * the v2hash, the server challenge, and + * the client nonce (random bits). + * + * We compose it from two parts: + * 1: 16-byte response hash + * 2: Client nonce + */ + lmresp = (uchar_t *)lm_mbp->mb_pos; + mb_put_mem(lm_mbp, NULL, NTLM_HASH_SZ); + err = ntlm_v2_resp_hash(lmresp, + v2hash, ctx->ct_ntlm_chal, + ctx->ct_clnonce, NTLM_CHAL_SZ); + if (err) + goto out; + mb_put_mem(lm_mbp, ctx->ct_clnonce, NTLM_CHAL_SZ); + + /* + * Compute the NTLMv2 response, derived + * from the server challenge and the + * "target info." blob passed in. + * + * Again composed from two parts: + * 1: 16-byte response hash + * 2: "target info." blob + */ + ntresp = (uchar_t *)nt_mbp->mb_pos; + mb_put_mem(nt_mbp, NULL, NTLM_HASH_SZ); + err = ntlm_v2_resp_hash(ntresp, + v2hash, ctx->ct_ntlm_chal, + (uchar_t *)tim->m_data, tim->m_len); + if (err) + goto out; + mb_put_mem(nt_mbp, tim->m_data, tim->m_len); + + /* + * Compute the session key + */ + ntlm_v2_session_key(ctx->ct_ssn_key, v2hash, ntresp); + +out: + if (err) { + mb_done(lm_mbp); + mb_done(nt_mbp); + } + free(ucdom); + free(ucuser); + + return (err); +} + +/* + * Helper for ntlm_build_target_info below. + * Put a name in the NTLMv2 "target info." blob. + */ +static void +smb_put_blob_name(struct mbdata *mbp, char *name, int type) +{ + uint16_t *ucs = NULL; + int nlen; + + if (name) + ucs = convert_utf8_to_leunicode(name); + if (ucs) + nlen = unicode_strlen(ucs); + else + nlen = 0; + + nlen <<= 1; /* length in bytes, without null. */ + + mb_put_uint16le(mbp, type); + mb_put_uint16le(mbp, nlen); + mb_put_mem(mbp, (char *)ucs, nlen); + + if (ucs) + free(ucs); +} + +/* + * Build an NTLMv2 "target info." blob. When called from NTLMSSP, + * the list of names comes from the Type 2 message. Otherwise, + * we create the name list here. + */ +int +ntlm_build_target_info(struct smb_ctx *ctx, struct mbuf *names, + struct mbdata *mbp) +{ + struct timeval now; + uint64_t nt_time; + + char *ucdom = NULL; /* user's domain */ + int err; + + /* Get mbuf chain for the "target info". */ + if ((err = mb_init(mbp, M_MINSIZE)) != 0) + return (err); + + /* + * Construct the client nonce by getting + * some random data from /dev/urandom + */ + err = smb_get_urandom(ctx->ct_clnonce, NTLM_CHAL_SZ); + if (err) + goto out; + + /* + * Get the "NT time" for the target info header. + */ + (void) gettimeofday(&now, 0); + smb_time_local2NT(&now, 0, &nt_time); + + /* + * Build the "target info." block. + * + * Based on information at: + * http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response + * + * First the fixed-size part. + */ + mb_put_uint32le(mbp, 0x101); /* Blob signature */ + mb_put_uint32le(mbp, 0); /* reserved */ + mb_put_uint64le(mbp, nt_time); /* NT time stamp */ + mb_put_mem(mbp, ctx->ct_clnonce, NTLM_CHAL_SZ); + mb_put_uint32le(mbp, 0); /* unknown */ + + /* + * Now put the list of names, either from the + * NTLMSSP Type 2 message or composed here. + */ + if (names) { + err = mb_put_mem(mbp, names->m_data, names->m_len); + } else { + /* Get upper-case names. */ + ucdom = utf8_str_toupper(ctx->ct_domain); + if (ucdom == NULL) { + err = ENOMEM; + goto out; + } + smb_put_blob_name(mbp, ucdom, NAMETYPE_DOMAIN_NB); + smb_put_blob_name(mbp, NULL, NAMETYPE_EOL); + /* OK, that's the whole "target info." blob! */ + } + err = 0; + +out: + free(ucdom); + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/ntlm.h b/usr/src/lib/libsmbfs/smb/ntlm.h new file mode 100644 index 0000000000..e8eae559e9 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ntlm.h @@ -0,0 +1,65 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _NTLM_H +#define _NTLM_H + +/* + * NTLM support functions + * See ntlm.c + */ + +/* + * Size of all LM/NTLM hashes, challenge + * NTLM_HASH_SZ: 16 bytes (see smb_lib.h) + * NTLM_CHAL_SZ: 8 bytes (see smb_lib.h) + */ +#define NTLM_V1_RESP_SZ 24 /* response size */ + +#define NAMETYPE_EOL 0x0000 /* end of list of names */ +#define NAMETYPE_MACHINE_NB 0x0001 /* NetBIOS machine name */ +#define NAMETYPE_DOMAIN_NB 0x0002 /* NetBIOS domain name */ +#define NAMETYPE_MACHINE_DNS 0x0003 /* DNS machine name */ +#define NAMETYPE_DOMAIN_DNS 0x0004 /* DNS (AD) domain name */ + +int +ntlm_compute_lm_hash(uchar_t *hash, const char *pw); + +int +ntlm_compute_nt_hash(uchar_t *hash, const char *pw); + +int +ntlm_build_target_info(struct smb_ctx *, struct mbuf *, struct mbdata *); + +int +ntlm_put_v1_responses(struct smb_ctx *ctx, + struct mbdata *lm_mbp, struct mbdata *nt_mbp); + +int +ntlm_put_v2_responses(struct smb_ctx *ctx, struct mbdata *ti_mbp, + struct mbdata *lm_mbp, struct mbdata *nt_mbp); + +#endif /* _NTLM_H */ diff --git a/usr/src/lib/libsmbfs/smb/ntlmssp.c b/usr/src/lib/libsmbfs/smb/ntlmssp.c new file mode 100644 index 0000000000..3428fbca36 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ntlmssp.c @@ -0,0 +1,634 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * NT Lan Manager Security Support Provider (NTLMSSP) + * + * Based on information from the "Davenport NTLM" page: + * http://davenport.sourceforge.net/ntlm.html + */ + + +#include <errno.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> +#include <netsmb/mchain.h> + +#include "private.h" +#include "charsets.h" +#include "spnego.h" +#include "derparse.h" +#include "ssp.h" +#include "ntlm.h" +#include "ntlmssp.h" + +typedef struct ntlmssp_state { + uint32_t ss_flags; + char *ss_target_name; + struct mbuf *ss_target_info; +} ntlmssp_state_t; + +/* + * So called "security buffer". + * A lot like an RPC string. + */ +struct sec_buf { + uint16_t sb_length; + uint16_t sb_maxlen; + uint32_t sb_offset; +}; +#define ID_SZ 8 +static const char ntlmssp_id[ID_SZ] = "NTLMSSP"; + +/* + * Get a "security buffer" (header part) + */ +static int +mb_get_sb_hdr(struct mbdata *mbp, struct sec_buf *sb) +{ + int err; + + (void) mb_get_uint16le(mbp, &sb->sb_length); + (void) mb_get_uint16le(mbp, &sb->sb_maxlen); + err = mb_get_uint32le(mbp, &sb->sb_offset); + + return (err); +} + +/* + * Get a "security buffer" (data part), where + * the data is delivered as an mbuf. + */ +static int +mb_get_sb_data(struct mbdata *mbp, struct sec_buf *sb, struct mbuf **mp) +{ + struct mbdata tmp_mb; + int err; + + /* + * Setup tmp_mb to point to the start of the header. + * This is a dup ref - do NOT free it. + */ + mb_initm(&tmp_mb, mbp->mb_top); + + /* Skip data up to the offset. */ + err = mb_get_mem(&tmp_mb, NULL, sb->sb_offset); + if (err) + return (err); + + /* Get the data (as an mbuf). */ + err = mb_get_mbuf(&tmp_mb, sb->sb_maxlen, mp); + + return (err); +} + +/* + * Put a "security buffer" (header part) + */ +static int +mb_put_sb_hdr(struct mbdata *mbp, struct sec_buf *sb) +{ + int err; + + (void) mb_put_uint16le(mbp, sb->sb_length); + (void) mb_put_uint16le(mbp, sb->sb_maxlen); + err = mb_put_uint32le(mbp, sb->sb_offset); + + return (err); +} + +/* + * Put a "security buffer" (data part), where + * the data is an mbuf. Note: consumes m. + */ +static int +mb_put_sb_data(struct mbdata *mbp, struct sec_buf *sb, struct mbuf *m) +{ + int cnt0, err; + + sb->sb_offset = cnt0 = mbp->mb_count; + err = mb_put_mbuf(mbp, m); + sb->sb_maxlen = sb->sb_length = mbp->mb_count - cnt0; + + return (err); +} + +/* + * Put a "security buffer" (data part), where + * the data is a string (OEM or unicode). + * + * The string is NOT null terminated. + */ +static int +mb_put_sb_string(struct mbdata *mbp, struct sec_buf *sb, + const char *s, int unicode) +{ + int err, trim; + struct mbdata tmp_mb; + + /* + * Put the string into a temp. mbuf, + * then chop off the null terminator + * before appending to caller's mbp. + */ + err = mb_init(&tmp_mb, M_MINSIZE); + if (err) + return (err); + err = mb_put_dstring(&tmp_mb, s, unicode); + if (err) + return (err); + + trim = (unicode) ? 2 : 1; + if (tmp_mb.mb_cur->m_len < trim) + return (EFAULT); + tmp_mb.mb_cur->m_len -= trim; + + err = mb_put_sb_data(mbp, sb, tmp_mb.mb_top); + /* + * Note: tmp_mb.mb_top is consumed, + * so do NOT free it (no mb_done) + */ + return (err); +} + +/* + * Build a Type 1 message + * + * This message has a header section containing offsets to + * data later in the message. We use the common trick of + * building it in two parts and then concatenatening. + */ +int +ntlmssp_put_type1(struct ssp_ctx *sp, struct mbdata *out_mb) +{ + struct type1hdr { + char h_id[ID_SZ]; + uint32_t h_type; + uint32_t h_flags; + struct sec_buf h_cldom; + struct sec_buf h_wksta; + } hdr; + struct mbdata mb2; /* 2nd part */ + int err; + struct smb_ctx *ctx = sp->smb_ctx; + ntlmssp_state_t *ssp_st = sp->sp_private; + char *ucdom = NULL; + char *ucwks = NULL; + + if ((err = mb_init(&mb2, M_MINSIZE)) != 0) + return (err); + mb2.mb_count = sizeof (hdr); + + /* + * Initialize the negotiation flags, and + * save what we sent. For reference: + * [MS-NLMP] spec. (also ntlmssp.h) + */ + ssp_st->ss_flags = + NTLMSSP_REQUEST_TARGET | + NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_TARGET_INFO | + NTLMSSP_NEGOTIATE_128 | + NTLMSSP_NEGOTIATE_56; + + if (ctx->ct_hflags2 & SMB_FLAGS2_UNICODE) + ssp_st->ss_flags |= NTLMSSP_NEGOTIATE_UNICODE; + else + ssp_st->ss_flags |= NTLMSSP_NEGOTIATE_OEM; + + if (ctx->ct_vcflags & SMBV_WILL_SIGN) { + ssp_st->ss_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE; + } + + bcopy(ntlmssp_id, &hdr.h_id, ID_SZ); + hdr.h_type = 1; /* Type 1 */ + hdr.h_flags = ssp_st->ss_flags; + + /* + * Put the client domain, client name strings. + * These are always in OEM format, upper-case. + */ + ucdom = utf8_str_toupper(ctx->ct_domain); + ucwks = utf8_str_toupper(ctx->ct_locname); + if (ucdom == NULL || ucwks == NULL) { + err = ENOMEM; + goto out; + } + err = mb_put_sb_string(&mb2, &hdr.h_cldom, ucdom, 0); + if (err) + goto out; + err = mb_put_sb_string(&mb2, &hdr.h_wksta, ucwks, 0); + if (err) + goto out; + + /* + * Marshal the header (in LE order) + * then concatenate the 2nd part. + */ + (void) mb_put_mem(out_mb, &hdr.h_id, ID_SZ); + (void) mb_put_uint32le(out_mb, hdr.h_type); + (void) mb_put_uint32le(out_mb, hdr.h_flags); + (void) mb_put_sb_hdr(out_mb, &hdr.h_cldom); + (void) mb_put_sb_hdr(out_mb, &hdr.h_wksta); + + err = mb_put_mbuf(out_mb, mb2.mb_top); + +out: + free(ucdom); + free(ucwks); + + return (err); +} + +/* + * Parse a Type 2 message + */ +int +ntlmssp_get_type2(struct ssp_ctx *sp, struct mbdata *in_mb) +{ + struct type2hdr { + char h_id[ID_SZ]; + uint32_t h_type; + struct sec_buf h_target_name; + uint32_t h_flags; + uint8_t h_challenge[8]; + uint32_t h_context[2]; /* optional */ + struct sec_buf h_target_info; /* optional */ + } hdr; + struct mbdata top_mb, tmp_mb; + struct mbuf *m; + int err, uc; + int min_hdr_sz = offsetof(struct type2hdr, h_context); + struct smb_ctx *ctx = sp->smb_ctx; + ntlmssp_state_t *ssp_st = sp->sp_private; + char *buf = NULL; + + if (m_totlen(in_mb->mb_top) < min_hdr_sz) { + err = EBADRPC; + goto out; + } + + /* + * Save the mbdata pointers before we consume anything. + * Careful to NOT free this (would be dup. free) + * We use this below to find data based on offsets + * from the start of the header. + */ + top_mb = *in_mb; + + /* Parse the fixed size header stuff. */ + bzero(&hdr, sizeof (hdr)); + (void) mb_get_mem(in_mb, &hdr.h_id, ID_SZ); + (void) mb_get_uint32le(in_mb, &hdr.h_type); + if (hdr.h_type != 2) { + err = EPROTO; + goto out; + } + (void) mb_get_sb_hdr(in_mb, &hdr.h_target_name); + (void) mb_get_uint32le(in_mb, &hdr.h_flags); + (void) mb_get_mem(in_mb, &hdr.h_challenge, NTLM_CHAL_SZ); + + /* + * Save flags, challenge for later. + */ + ssp_st->ss_flags = hdr.h_flags; + uc = hdr.h_flags & NTLMSSP_NEGOTIATE_UNICODE; + bcopy(&hdr.h_challenge, ctx->ct_ntlm_chal, NTLM_CHAL_SZ); + + /* + * Now find out if the optional parts are there. + */ + if ((m_totlen(top_mb.mb_top) > sizeof (hdr)) && + (hdr.h_target_name.sb_offset >= sizeof (hdr))) { + (void) mb_get_uint32le(in_mb, &hdr.h_context[0]); + (void) mb_get_uint32le(in_mb, &hdr.h_context[1]); + (void) mb_get_sb_hdr(in_mb, &hdr.h_target_info); + } + + /* + * Get the target name string. First get a copy of + * the data from the offset/length indicated in the + * security buffer header; then parse the string. + */ + err = mb_get_sb_data(&top_mb, &hdr.h_target_name, &m); + if (err) + goto out; + mb_initm(&tmp_mb, m); + err = mb_get_string(&tmp_mb, &ssp_st->ss_target_name, uc); + mb_done(&tmp_mb); + + /* + * Get the target info blob, if present. + */ + if (hdr.h_target_info.sb_offset >= sizeof (hdr)) { + err = mb_get_sb_data(&top_mb, &hdr.h_target_info, + &ssp_st->ss_target_info); + } + +out: + if (buf != NULL) + free(buf); + + return (err); +} + +/* + * Build a Type 3 message + * + * This message has a header section containing offsets to + * data later in the message. We use the common trick of + * building it in two parts and then concatenatening. + */ +int +ntlmssp_put_type3(struct ssp_ctx *sp, struct mbdata *out_mb) +{ + struct type3hdr { + char h_id[ID_SZ]; + uint32_t h_type; + struct sec_buf h_lm_resp; + struct sec_buf h_nt_resp; + struct sec_buf h_domain; + struct sec_buf h_user; + struct sec_buf h_wksta; + } hdr; + struct mbdata lm_mbc, nt_mbc, ti_mbc; + struct mbdata mb2; /* 2nd part */ + int err, uc; + char *ucdom = NULL; /* user's domain */ + char *ucuser = NULL; /* user name */ + char *ucwksta = NULL; /* workstation */ + struct smb_ctx *ctx = sp->smb_ctx; + ntlmssp_state_t *ssp_st = sp->sp_private; + + bzero(&lm_mbc, sizeof (lm_mbc)); + bzero(&nt_mbc, sizeof (nt_mbc)); + bzero(&ti_mbc, sizeof (ti_mbc)); + bzero(&mb2, sizeof (mb2)); + + /* + * Convert the user name to upper-case, as that's what's + * used when computing LMv2 and NTLMv2 responses. Also + * domain, workstation + */ + ucdom = utf8_str_toupper(ctx->ct_domain); + ucuser = utf8_str_toupper(ctx->ct_user); + ucwksta = utf8_str_toupper(ctx->ct_locname); + if (ucdom == NULL || ucuser == NULL || ucwksta == NULL) { + err = ENOMEM; + goto out; + } + + if ((err = mb_init(&mb2, M_MINSIZE)) != 0) + goto out; + mb2.mb_count = sizeof (hdr); + uc = ssp_st->ss_flags & NTLMSSP_NEGOTIATE_UNICODE; + + bcopy(ntlmssp_id, &hdr.h_id, ID_SZ); + hdr.h_type = 3; /* Type 3 */ + + /* + * Put the LMv2,NTLMv2 responses, or + * possibly LM, NTLM (v1) responses. + */ + if (ctx->ct_authflags & SMB_AT_NTLM2) { + /* Build the NTLMv2 "target info" blob. */ + err = ntlm_build_target_info(ctx, + ssp_st->ss_target_info, &ti_mbc); + if (err) + goto out; + err = ntlm_put_v2_responses(ctx, &ti_mbc, + &lm_mbc, &nt_mbc); + } else { + err = ntlm_put_v1_responses(ctx, + &lm_mbc, &nt_mbc); + } + if (err) + goto out; + + err = mb_put_sb_data(&mb2, &hdr.h_lm_resp, lm_mbc.mb_top); + lm_mbc.mb_top = NULL; /* consumed */ + if (err) + goto out; + err = mb_put_sb_data(&mb2, &hdr.h_nt_resp, nt_mbc.mb_top); + nt_mbc.mb_top = NULL; /* consumed */ + if (err) + goto out; + + /* + * Put the "target" (domain), user, workstation + */ + err = mb_put_sb_string(&mb2, &hdr.h_domain, ucdom, uc); + if (err) + goto out; + err = mb_put_sb_string(&mb2, &hdr.h_user, ucuser, uc); + if (err) + goto out; + err = mb_put_sb_string(&mb2, &hdr.h_wksta, ucwksta, uc); + if (err) + goto out; + + /* + * Marshal the header (in LE order) + * then concatenate the 2nd part. + */ + (void) mb_put_mem(out_mb, &hdr.h_id, ID_SZ); + (void) mb_put_uint32le(out_mb, hdr.h_type); + + (void) mb_put_sb_hdr(out_mb, &hdr.h_lm_resp); + (void) mb_put_sb_hdr(out_mb, &hdr.h_nt_resp); + + (void) mb_put_sb_hdr(out_mb, &hdr.h_domain); + (void) mb_put_sb_hdr(out_mb, &hdr.h_user); + (void) mb_put_sb_hdr(out_mb, &hdr.h_wksta); + + err = mb_put_mbuf(out_mb, mb2.mb_top); + mb2.mb_top = NULL; /* consumed */ + +out: + free(ucdom); + free(ucuser); + free(ucwksta); + + mb_done(&mb2); + mb_done(&lm_mbc); + mb_done(&nt_mbc); + + return (err); +} + +/* + * ntlmssp_final + * + * Called after successful authentication. + * Setup the MAC key for signing. + */ +int +ntlmssp_final(struct ssp_ctx *sp) +{ + struct smb_ctx *ctx = sp->smb_ctx; + int err = 0; + + /* + * MAC_key is just the session key, but + * Only on the first successful auth. + */ + if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) && + (ctx->ct_mackey == NULL)) { + ctx->ct_mackeylen = NTLM_HASH_SZ; + ctx->ct_mackey = malloc(ctx->ct_mackeylen); + if (ctx->ct_mackey == NULL) { + ctx->ct_mackeylen = 0; + err = ENOMEM; + goto out; + } + memcpy(ctx->ct_mackey, ctx->ct_ssn_key, NTLM_HASH_SZ); + /* + * Apparently, the server used seq. no. zero + * for our previous message, so next is two. + */ + ctx->ct_mac_seqno = 2; + } + +out: + return (err); +} + +/* + * ntlmssp_next_token + * + * See ssp.c: ssp_ctx_next_token + */ +int +ntlmssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb, + struct mbdata *out_mb) +{ + int err; + + if (out_mb == NULL) { + /* final call on successful auth. */ + err = ntlmssp_final(sp); + goto out; + } + + /* Will build an ouptut token. */ + err = mb_init(out_mb, M_MINSIZE); + if (err) + goto out; + + /* + * When called with in_mb == NULL, it means + * this is the first call for this session, + * so put a Type 1 (initialize) token. + */ + if (in_mb == NULL) { + err = ntlmssp_put_type1(sp, out_mb); + goto out; + } + + /* + * This is not the first call, so + * parse the response token we received. + * It should be a Type 2 (challenge). + * Then put a Type 3 (authenticate) + */ + err = ntlmssp_get_type2(sp, in_mb); + if (err) + goto out; + + err = ntlmssp_put_type3(sp, out_mb); + +out: + if (err) + DPRINT("ret: %d", err); + return (err); +} + +/* + * ntlmssp_ctx_destroy + * + * Destroy mechanism-specific data. + */ +void +ntlmssp_destroy(struct ssp_ctx *sp) +{ + ntlmssp_state_t *ssp_st; + + ssp_st = sp->sp_private; + if (ssp_st != NULL) { + sp->sp_private = NULL; + free(ssp_st->ss_target_name); + m_freem(ssp_st->ss_target_info); + free(ssp_st); + } +} + +/* + * ntlmssp_init_clnt + * + * Initialize a new NTLMSSP client context. + */ +int +ntlmssp_init_client(struct ssp_ctx *sp) +{ + ntlmssp_state_t *ssp_st; + + if ((sp->smb_ctx->ct_authflags & + (SMB_AT_NTLM2 | SMB_AT_NTLM1)) == 0) { + DPRINT("No NTLM authflags"); + return (ENOTSUP); + } + + ssp_st = calloc(1, sizeof (*ssp_st)); + if (ssp_st == NULL) + return (ENOMEM); + + sp->sp_nexttok = ntlmssp_next_token; + sp->sp_destroy = ntlmssp_destroy; + sp->sp_private = ssp_st; + + return (0); +} diff --git a/usr/src/lib/libsmbfs/smb/ntlmssp.h b/usr/src/lib/libsmbfs/smb/ntlmssp.h new file mode 100644 index 0000000000..591b1ab088 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ntlmssp.h @@ -0,0 +1,76 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _NTLMSSP_H +#define _NTLMSSP_H + +/* + * NT LanMan Security Support Package (NTLMSSP) + * Negotiation flags, etc. + * + * Reference: [MS-NLMP] NT LAN Manager (NTLM) + * Authentication Protocol Specification + * http://msdn.microsoft.com/en-us/library/cc236621(PROT.10).aspx + */ + +/* + * NTLMSSP Negotiate Flags + * [MS-NLMP] sec. 2.2.2.5 + */ +#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 +#define NTLMSSP_NEGOTIATE_OEM 0x00000002 +#define NTLMSSP_REQUEST_TARGET 0x00000004 +/* reserved 0x00000008 */ +#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 +#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 +#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040 +#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080 +/* reserved 0x00000100 */ +#define NTLMSSP_NEGOTIATE_NTLM 0x00000200 +#define NTLMSSP_NEGOTIATE_NT_ONLY 0x00000400 +/* old anonymous_session (ignored by servers) 0x00000800 */ +#define NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED 0x00001000 +#define NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED 0x00002000 +/* reserved 0x00004000 */ +#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000 +#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000 +#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000 +#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000 +#define NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 0x00080000 +#define NTLMSSP_NEGOTIATE_IDENTIFY 0x00100000 +/* reserved 0x00200000 */ +#define NTLMSSP_REQUEST_NON_NT_SESSION_KEY 0x00400000 +#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000 +/* reserved 0x01000000 */ +#define NTLMSSP_NEGOTIATE_VERSION 0x02000000 +/* reserved 0x04000000 */ +/* reserved 0x08000000 */ +/* reserved 0x10000000 */ +#define NTLMSSP_NEGOTIATE_128 0x20000000 +#define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000 +#define NTLMSSP_NEGOTIATE_56 0x80000000 + +#endif /* _NTLMSSP_H */ diff --git a/usr/src/lib/libsmbfs/smb/print.c b/usr/src/lib/libsmbfs/smb/print.c index e0b18e8c40..263cafaa93 100644 --- a/usr/src/lib/libsmbfs/smb/print.c +++ b/usr/src/lib/libsmbfs/smb/print.c @@ -46,50 +46,77 @@ #include <grp.h> #include <unistd.h> +#include <netsmb/smb.h> #include <netsmb/smb_lib.h> -#include <cflib.h> #include "private.h" int -smb_smb_open_print_file(struct smb_ctx *ctx, int setuplen, int mode, - const char *ident, smbfh *fhp) +smb_printer_open(struct smb_ctx *ctx, int setuplen, int mode, + const char *ident, int *fhp) { struct smb_rq *rqp; struct mbdata *mbp; - int error; + int error, flags2, uc; + uint16_t fh; + uint8_t wc; + + flags2 = smb_ctx_flags2(ctx); + if (flags2 == -1) + return (EIO); + uc = flags2 & SMB_FLAGS2_UNICODE; - error = smb_rq_init(ctx, SMB_COM_OPEN_PRINT_FILE, 2, &rqp); + error = smb_rq_init(ctx, SMB_COM_OPEN_PRINT_FILE, &rqp); if (error) return (error); mbp = smb_rq_getrequest(rqp); + smb_rq_wstart(rqp); mb_put_uint16le(mbp, setuplen); mb_put_uint16le(mbp, mode); smb_rq_wend(rqp); + smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); - smb_rq_dstring(mbp, ident); + mb_put_dstring(mbp, ident, uc); + smb_rq_bend(rqp); error = smb_rq_simple(rqp); - if (!error) { - mbp = smb_rq_getreply(rqp); - mb_get_uint16(mbp, fhp); + if (error) + goto out; + + mbp = smb_rq_getreply(rqp); + error = mb_get_uint8(mbp, &wc); + if (error || wc < 1) { + error = EBADRPC; + goto out; } + mb_get_uint16(mbp, &fh); + *fhp = fh; + error = 0; + +out: smb_rq_done(rqp); return (error); } +/* + * Similar to smb_fh_close + */ int -smb_smb_close_print_file(struct smb_ctx *ctx, smbfh fh) +smb_printer_close(struct smb_ctx *ctx, int fh) { struct smb_rq *rqp; struct mbdata *mbp; int error; - error = smb_rq_init(ctx, SMB_COM_CLOSE_PRINT_FILE, 0, &rqp); + error = smb_rq_init(ctx, SMB_COM_CLOSE_PRINT_FILE, &rqp); if (error) return (error); mbp = smb_rq_getrequest(rqp); - mb_put_mem(mbp, (char *)&fh, 2); + smb_rq_wstart(rqp); + mb_put_uint16le(mbp, (uint16_t)fh); smb_rq_wend(rqp); + mb_put_uint16le(mbp, 0); /* byte count */ + error = smb_rq_simple(rqp); smb_rq_done(rqp); + return (error); } diff --git a/usr/src/lib/libsmbfs/smb/private.h b/usr/src/lib/libsmbfs/smb/private.h index 6630ed160a..b95ec1f7f4 100644 --- a/usr/src/lib/libsmbfs/smb/private.h +++ b/usr/src/lib/libsmbfs/smb/private.h @@ -31,7 +31,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -44,6 +44,33 @@ */ #include <inttypes.h> +#include <sys/byteorder.h> +#include <sys/ccompile.h> + +#include <netsmb/netbios.h> + +extern void dprint(const char *, const char *, ...) + __PRINTFLIKE(2); + +#if defined(DEBUG) || defined(__lint) +#define DPRINT(...) dprint(__func__, __VA_ARGS__) +#else +#define DPRINT(...) ((void)0) +#endif + +/* + * Flags bits in ct_vcflags (copied from smb_conn.h) + * Pass these to the driver? + */ +#define SMBV_RECONNECTING 0x0002 /* conn in process of reconnection */ +#define SMBV_LONGNAMES 0x0004 /* conn configured to use long names */ +#define SMBV_ENCRYPT 0x0008 /* server demands encrypted password */ +#define SMBV_WIN95 0x0010 /* used to apply bugfixes for this OS */ +#define SMBV_NT4 0x0020 /* used when NT4 issues invalid resp */ +#define SMBV_UNICODE 0x0040 /* conn configured to use Unicode */ +#define SMBV_EXT_SEC 0x0080 /* conn to use extended security */ +#define SMBV_WILL_SIGN 0x0100 /* negotiated signing */ + /* * BSD-style mbuf simulation @@ -56,7 +83,6 @@ struct mbuf { }; typedef struct mbuf mbuf_t; -#if 0 /* in smb_lib.h */ struct mbdata { struct mbuf *mb_top; struct mbuf *mb_cur; @@ -64,12 +90,16 @@ struct mbdata { int mb_count; }; typedef struct mbdata mbdata_t; -#endif +/* + * Note: Leaving a little space (8 bytes) between the + * mbuf header and the start of the data so we can + * prepend a NetBIOS header in that space. + */ #define M_ALIGNFACTOR (sizeof (long)) #define M_ALIGN(len) (((len) + M_ALIGNFACTOR - 1) & ~(M_ALIGNFACTOR - 1)) -#define M_BASESIZE (sizeof (struct mbuf)) -#define M_MINSIZE (256 - M_BASESIZE) +#define M_BASESIZE (sizeof (struct mbuf) + 8) +#define M_MINSIZE (1024 - M_BASESIZE) #define M_TOP(m) ((char *)(m) + M_BASESIZE) #define M_TRAILINGSPACE(m) ((m)->m_maxlen - (m)->m_len) #define mtod(m, t) ((t)(m)->m_data) @@ -78,32 +108,51 @@ typedef struct mbdata mbdata_t; * request handling structures */ struct smb_rq { - uchar_t rq_cmd; + struct smb_ctx *rq_ctx; struct mbdata rq_rq; struct mbdata rq_rp; - struct smb_ctx *rq_ctx; - int rq_wcount; - int rq_bcount; + int rq_rpbufsz; + uint8_t rq_cmd; + uint8_t rq_hflags; + uint16_t rq_hflags2; + uint32_t rq_status; + uint16_t rq_uid; + uint16_t rq_tid; + uint16_t rq_mid; + uint32_t rq_seqno; + /* See rq_[bw]{start,end} functions */ + char *rq_wcntp; + int rq_wcbase; + char *rq_bcntp; + int rq_bcbase; }; typedef struct smb_rq smb_rq_t; #define smb_rq_getrequest(rqp) (&(rqp)->rq_rq) #define smb_rq_getreply(rqp) (&(rqp)->rq_rp) -int smb_rq_init(struct smb_ctx *, uchar_t, size_t, struct smb_rq **); +int smb_rq_init(struct smb_ctx *, uchar_t, struct smb_rq **); void smb_rq_done(struct smb_rq *); +void smb_rq_bstart(struct smb_rq *); +void smb_rq_bend(struct smb_rq *); +void smb_rq_wstart(struct smb_rq *); void smb_rq_wend(struct smb_rq *); int smb_rq_simple(struct smb_rq *); int smb_rq_dmem(struct mbdata *, const char *, size_t); -int smb_rq_dstring(struct mbdata *, const char *); +int smb_rq_internal(struct smb_ctx *, struct smb_rq *); +int smb_rq_sign(struct smb_rq *); +int smb_rq_verify(struct smb_rq *); /* * Message compose/parse */ +void m_freem(struct mbuf *); int m_getm(struct mbuf *, size_t, struct mbuf **); int m_lineup(struct mbuf *, struct mbuf **); +size_t m_totlen(struct mbuf *); + int mb_init(struct mbdata *, size_t); int mb_initm(struct mbdata *, struct mbuf *); int mb_done(struct mbdata *); @@ -115,9 +164,11 @@ int mb_put_uint32be(struct mbdata *, uint32_t); int mb_put_uint32le(struct mbdata *, uint32_t); int mb_put_uint64be(struct mbdata *, uint64_t); int mb_put_uint64le(struct mbdata *, uint64_t); -int mb_put_mem(struct mbdata *, const char *, size_t); -int mb_put_pstring(struct mbdata *mbp, const char *s); +int mb_put_mem(struct mbdata *, const void *, size_t); int mb_put_mbuf(struct mbdata *, struct mbuf *); +int mb_put_astring(struct mbdata *mbp, const char *s); +int mb_put_dstring(struct mbdata *mbp, const char *s, int); +int mb_put_ustring(struct mbdata *mbp, const char *s); int mb_get_uint8(struct mbdata *, uint8_t *); int mb_get_uint16(struct mbdata *, uint16_t *); @@ -129,26 +180,82 @@ int mb_get_uint32le(struct mbdata *, uint32_t *); int mb_get_uint64(struct mbdata *, uint64_t *); int mb_get_uint64be(struct mbdata *, uint64_t *); int mb_get_uint64le(struct mbdata *, uint64_t *); -int mb_get_mem(struct mbdata *, char *, size_t); +int mb_get_mem(struct mbdata *, void *, size_t); +int mb_get_mbuf(struct mbdata *, int, struct mbuf **); +int mb_get_string(struct mbdata *, char **, int); +int mb_get_astring(struct mbdata *, char **); +int mb_get_ustring(struct mbdata *, char **); + /* * Network stuff (NetBIOS and otherwise) */ +struct nb_name; +struct sockaddr_nb; + +extern int smb_recv_timeout; /* seconds */ + +void dump_ctx(char *, struct smb_ctx *); +void dump_addrinfo(struct addrinfo *); +void dump_sockaddr(struct sockaddr *); +int nb_ssn_request(struct smb_ctx *, char *); int nb_name_len(struct nb_name *); -/* new flag UCflag. 1=uppercase,0=don't */ -int nb_name_encode(struct nb_name *, uchar_t *); +int nb_name_encode(struct mbdata *, struct nb_name *); int nb_encname_len(const uchar_t *); -int nb_snballoc(int namelen, struct sockaddr_nb **); +int nb_snballoc(struct sockaddr_nb **); void nb_snbfree(struct sockaddr *); int nb_sockaddr(struct sockaddr *, struct nb_name *, struct sockaddr_nb **); +int nbns_getaddrinfo(const char *name, struct nb_ctx *nbc, + struct addrinfo **res); int nbns_resolvename(const char *, struct nb_ctx *, struct sockaddr **); -int nbns_getnodestatus(struct sockaddr *targethost, - struct nb_ctx *ctx, char *system, char *workgroup); -int nb_getlocalname(char *name, size_t maxlen); +int get_xti_err(int); + + +/* + * Private SMB stuff + */ + +struct smb_bitname { + uint_t bn_bit; + char *bn_name; +}; +typedef struct smb_bitname smb_bitname_t; +char *smb_printb(char *, int, const struct smb_bitname *); + +int smb_ctx_getaddr(struct smb_ctx *ctx); +int smb_ctx_gethandle(struct smb_ctx *ctx); + +int smb_ssn_send(struct smb_ctx *, struct mbdata *); +int smb_ssn_recv(struct smb_ctx *, struct mbdata *); + +int smb_negprot(struct smb_ctx *, struct mbdata *); + +int smb_ssnsetup_null(struct smb_ctx *); +int smb_ssnsetup_ntlm1(struct smb_ctx *); +int smb_ssnsetup_ntlm2(struct smb_ctx *); +int smb_ssnsetup_spnego(struct smb_ctx *, struct mbdata *); + +void smb_time_local2server(struct timeval *, int, long *); +void smb_time_server2local(ulong_t, int, struct timeval *); +void smb_time_NT2local(uint64_t, int, struct timeval *); +void smb_time_local2NT(struct timeval *, int, uint64_t *); + +int smb_getlocalname(char **); +int smb_get_authentication(struct smb_ctx *); +int smb_get_keychain(struct smb_ctx *ctx); +void smb_hexdump(const void *buf, int len); + +/* See ssp.c */ +int ssp_ctx_create_client(struct smb_ctx *, struct mbdata *); +int ssp_ctx_next_token(struct smb_ctx *, struct mbdata *, struct mbdata *); +void ssp_ctx_destroy(struct smb_ctx *); +#ifdef KICONV_SUPPORT +/* See nls.c (get rid of this?) */ extern uchar_t nls_lower[256], nls_upper[256]; +#endif /* KICONV_SUPPORT */ #endif /* _PRIVATE_H */ diff --git a/usr/src/lib/libsmbfs/smb/rap.c b/usr/src/lib/libsmbfs/smb/rap.c index 3a9e785191..8260e2639c 100644 --- a/usr/src/lib/libsmbfs/smb/rap.c +++ b/usr/src/lib/libsmbfs/smb/rap.c @@ -204,7 +204,6 @@ smb_rap_create(int fn, const char *param, const char *data, struct smb_rap *rap; char *p; int plen = 0, len = 0; - int i; rap = malloc(sizeof (*rap)); if (rap == NULL) @@ -326,10 +325,10 @@ int smb_rap_request(struct smb_rap *rap, struct smb_ctx *ctx) { uint16_t *rp, conv, *tmp; - uint32_t *p32, ps1; + uint32_t *p32; char *dp, *p = rap->r_nparam; char ptype; - int error, rdatacnt, rparamcnt, entries, done, dlen, buffer_oflow, i; + int error, rdatacnt, rparamcnt, entries, done, dlen, buffer_oflow; rdatacnt = rap->r_rcvbuflen; rparamcnt = rap->r_plen; diff --git a/usr/src/lib/libsmbfs/smb/rcfile.c b/usr/src/lib/libsmbfs/smb/rcfile.c index 3f5a87435d..22ca0fc420 100644 --- a/usr/src/lib/libsmbfs/smb/rcfile.c +++ b/usr/src/lib/libsmbfs/smb/rcfile.c @@ -36,45 +36,61 @@ #include <sys/types.h> #include <sys/queue.h> #include <sys/stat.h> + #include <ctype.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <strings.h> #include <stdlib.h> -#include <libintl.h> -#include <pwd.h> +#include <synch.h> #include <unistd.h> -#include <sys/debug.h> +#include <pwd.h> +#include <libintl.h> #include <cflib.h> #include "rcfile_priv.h" -extern int smb_debug; -SLIST_HEAD(rcfile_head, rcfile); -static struct rcfile_head pf_head = {NULL}; +#include <assert.h> + +#if 0 /* before SMF */ +#define SMB_CFG_FILE "/etc/nsmb.conf" +#define OLD_SMB_CFG_FILE "/usr/local/etc/nsmb.conf" +#endif +#define SMBFS_SHARECTL_CMD "/usr/sbin/sharectl get smbfs" + +extern int smb_debug; static struct rcfile *rc_cachelookup(const char *filename); -struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname); +static struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname); static struct rcsection *rc_addsect(struct rcfile *rcp, const char *sectname); -static int rc_freesect(struct rcfile *rcp, struct rcsection *rsp); -struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *keyname); +static int rc_freesect(struct rcfile *rcp, struct rcsection *rsp); +static struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *key); static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value); static void rc_key_free(struct rckey *p); static void rc_parse(struct rcfile *rcp); +/* lock for the variables below */ +mutex_t rcfile_mutex = DEFAULTMUTEX; + +SLIST_HEAD(rcfile_head, rcfile); +static struct rcfile_head pf_head = {NULL}; +struct rcfile *smb_rc; +int home_nsmbrc; int insecure_nsmbrc; /* * open rcfile and load its content, if already open - return previous handle */ -int +static int rc_open(const char *filename, const char *mode, struct rcfile **rcfile) { + struct stat statbuf; struct rcfile *rcp; FILE *f; - struct stat statbuf; + + assert(MUTEX_HELD(&rcfile_mutex)); rcp = rc_cachelookup(filename); if (rcp) { @@ -102,12 +118,15 @@ rc_open(const char *filename, const char *mode, struct rcfile **rcfile) return (0); } -int +static int rc_merge(const char *filename, struct rcfile **rcfile) { + struct stat statbuf; struct rcfile *rcp = *rcfile; FILE *f, *t; + assert(MUTEX_HELD(&rcfile_mutex)); + insecure_nsmbrc = 0; if (rcp == NULL) { return (rc_open(filename, "r", rcfile)); @@ -115,6 +134,10 @@ rc_merge(const char *filename, struct rcfile **rcfile) f = fopen(filename, "r"); if (f == NULL) return (errno); + insecure_nsmbrc = 0; + if (fstat(fileno(f), &statbuf) >= 0 && + (statbuf.st_mode & 077) != 0) + insecure_nsmbrc = 1; t = rcp->rf_f; rcp->rf_f = f; rc_parse(rcp); @@ -123,43 +146,45 @@ rc_merge(const char *filename, struct rcfile **rcfile) return (0); } -int -rc_merge_pipe(const char *command, struct rcfile **rcfile) +/* + * Like rc_open, but does popen of command: + * sharectl get smbfs + */ +static int +rc_popen_cmd(const char *command, struct rcfile **rcfile) { - struct rcfile *rcp = *rcfile; - FILE *f, *t; + struct rcfile *rcp; + FILE *f; + + assert(MUTEX_HELD(&rcfile_mutex)); - insecure_nsmbrc = 0; f = popen(command, "r"); if (f == NULL) return (errno); + insecure_nsmbrc = 0; + + rcp = malloc(sizeof (struct rcfile)); if (rcp == NULL) { - rcp = malloc(sizeof (struct rcfile)); - if (rcp == NULL) { - fclose(f); - return (ENOMEM); - } - *rcfile = rcp; - bzero(rcp, sizeof (struct rcfile)); - rcp->rf_name = strdup(command); - rcp->rf_f = f; - SLIST_INSERT_HEAD(&pf_head, rcp, rf_next); - rc_parse(rcp); - } else { - t = rcp->rf_f; - rcp->rf_f = f; - rc_parse(rcp); - rcp->rf_f = t; + fclose(f); + return (ENOMEM); } - fclose(f); + bzero(rcp, sizeof (struct rcfile)); + rcp->rf_name = strdup(command); + rcp->rf_f = f; + SLIST_INSERT_HEAD(&pf_head, rcp, rf_next); + rc_parse(rcp); + *rcfile = rcp; + /* fclose(f) in rc_close */ return (0); } -int +static int rc_close(struct rcfile *rcp) { struct rcsection *p, *n; + mutex_lock(&rcfile_mutex); + fclose(rcp->rf_f); for (p = SLIST_FIRST(&rcp->rf_sect); p; ) { n = p; @@ -169,6 +194,8 @@ rc_close(struct rcfile *rcp) free(rcp->rf_name); SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next); free(rcp); + + mutex_unlock(&rcfile_mutex); return (0); } @@ -177,17 +204,21 @@ rc_cachelookup(const char *filename) { struct rcfile *p; + assert(MUTEX_HELD(&rcfile_mutex)); + SLIST_FOREACH(p, &pf_head, rf_next) if (strcmp(filename, p->rf_name) == 0) return (p); return (0); } -/* static */ struct rcsection * +static struct rcsection * rc_findsect(struct rcfile *rcp, const char *sectname) { struct rcsection *p; + assert(MUTEX_HELD(&rcfile_mutex)); + SLIST_FOREACH(p, &rcp->rf_sect, rs_next) if (strcasecmp(p->rs_name, sectname) == 0) return (p); @@ -199,6 +230,8 @@ rc_addsect(struct rcfile *rcp, const char *sectname) { struct rcsection *p; + assert(MUTEX_HELD(&rcfile_mutex)); + p = rc_findsect(rcp, sectname); if (p) return (p); @@ -216,6 +249,8 @@ rc_freesect(struct rcfile *rcp, struct rcsection *rsp) { struct rckey *p, *n; + assert(MUTEX_HELD(&rcfile_mutex)); + SLIST_REMOVE(&rcp->rf_sect, rsp, rcsection, rs_next); for (p = SLIST_FIRST(&rsp->rs_keys); p; ) { n = p; @@ -227,11 +262,13 @@ rc_freesect(struct rcfile *rcp, struct rcsection *rsp) return (0); } -/* static */ struct rckey * +static struct rckey * rc_sect_findkey(struct rcsection *rsp, const char *keyname) { struct rckey *p; + assert(MUTEX_HELD(&rcfile_mutex)); + SLIST_FOREACH(p, &rsp->rs_keys, rk_next) if (strcmp(p->rk_name, keyname) == 0) return (p); @@ -243,6 +280,8 @@ rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value) { struct rckey *p; + assert(MUTEX_HELD(&rcfile_mutex)); + p = rc_sect_findkey(rsp, name); if (!p) { p = malloc(sizeof (*p)); @@ -273,16 +312,13 @@ rc_key_free(struct rckey *p) free(p); } -enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue}; - -int home_nsmbrc = 0; -static char *minauth[] = { - "kerberos", - "ntlmv2", - "ntlm", - "lm", +static char *minauth_values[] = { "none", + "lm", + "ntlm", + "ntlmv2", + "kerberos", NULL }; @@ -291,43 +327,57 @@ eval_minauth(char *auth) { int i; - for (i = 0; minauth[i]; i++) - if (strcmp(auth, minauth[i]) == 0) - break; - return (i); + for (i = 0; minauth_values[i]; i++) + if (strcmp(auth, minauth_values[i]) == 0) + return (i); + return (-1); } /* - * Ensure that "minauth" is set to the highest level (lowest array offset) + * Ensure that "minauth" is set to the highest level */ +/*ARGSUSED*/ static void set_value(struct rcfile *rcp, struct rcsection *rsp, struct rckey *rkp, char *ptr) { int now, new; +#ifdef DEBUG + char *from; + + if (smb_debug) + from = (home_nsmbrc) ? + "user file" : "SMF"; +#endif if (strcmp(rkp->rk_name, "minauth") == 0) { now = eval_minauth(rkp->rk_value); new = eval_minauth(ptr); - if (new >= now) { + if (new <= now) { #ifdef DEBUG if (smb_debug) - printf( - "set_value: rejecting %s=%s from %s\n", - rkp->rk_name, ptr, home_nsmbrc ? - "user file" : "SMF"); + fprintf(stderr, + "set_value: rejecting %s=%s" + " in %s from %s\n", + rkp->rk_name, ptr, + rsp->rs_name, from); #endif return; } } #ifdef DEBUG if (smb_debug) - printf("set_value: applying %s=%s from %s\n", - rkp->rk_name, ptr, home_nsmbrc ? "user file" : "SMF"); + fprintf(stderr, + "set_value: applying %s=%s in %s from %s\n", + rkp->rk_name, ptr, rsp->rs_name, from); #endif rkp->rk_value = strdup(ptr); } + +/* states in rc_parse */ +enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue}; + static void rc_parse(struct rcfile *rcp) { @@ -338,6 +388,8 @@ rc_parse(struct rcfile *rcp) char buf[2048]; char *next = buf, *last = &buf[sizeof (buf)-1]; + assert(MUTEX_HELD(&rcfile_mutex)); + while ((c = getc(f)) != EOF) { if (c == '\r') continue; @@ -393,8 +445,8 @@ rc_parse(struct rcfile *rcp) state = stSkipToEOL; continue; } - if (home_nsmbrc && - (strcmp(buf, "nbns") == 0 || + if (home_nsmbrc != 0 && ( + strcmp(buf, "nbns") == 0 || strcmp(buf, "nbns_enable") == 0 || strcmp(buf, "nbns_broadcast") == 0 || strcmp(buf, "signing") == 0)) { @@ -405,7 +457,8 @@ rc_parse(struct rcfile *rcp) state = stNewLine; continue; } - if (insecure_nsmbrc && (strcmp(buf, "password") == 0)) { + if (insecure_nsmbrc != 0 && + strcmp(buf, "password") == 0) { fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: .nsmbrc file not secure, " "ignoring passwords\n")); @@ -445,16 +498,27 @@ rc_getstringptr(struct rcfile *rcp, const char *section, const char *key, { struct rcsection *rsp; struct rckey *rkp; + int err; + + mutex_lock(&rcfile_mutex); *dest = NULL; rsp = rc_findsect(rcp, section); - if (!rsp) - return (ENOENT); + if (!rsp) { + err = ENOENT; + goto out; + } rkp = rc_sect_findkey(rsp, key); - if (!rkp) - return (ENOENT); + if (!rkp) { + err = ENOENT; + goto out; + } *dest = rkp->rk_value; - return (0); + err = 0; + +out: + mutex_unlock(&rcfile_mutex); + return (err); } int @@ -468,7 +532,7 @@ rc_getstring(struct rcfile *rcp, const char *section, const char *key, if (error) return (error); if (strlen(value) >= maxlen) { - fprintf(stdout, dgettext(TEXT_DOMAIN, + fprintf(stderr, dgettext(TEXT_DOMAIN, "line too long for key '%s' in section '%s', max = %d\n"), key, section, maxlen); return (EINVAL); @@ -482,22 +546,31 @@ rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value) { struct rcsection *rsp; struct rckey *rkp; + int err; + + mutex_lock(&rcfile_mutex); rsp = rc_findsect(rcp, section); - if (!rsp) - return (ENOENT); + if (!rsp) { + err = ENOENT; + goto out; + } rkp = rc_sect_findkey(rsp, key); - if (!rkp) - return (ENOENT); + if (!rkp) { + err = ENOENT; + goto out; + } errno = 0; *value = strtol(rkp->rk_value, NULL, 0); - if (errno) { - fprintf(stdout, dgettext(TEXT_DOMAIN, + if ((err = errno) != 0) { + fprintf(stderr, dgettext(TEXT_DOMAIN, "invalid int value '%s' for key '%s' in section '%s'\n"), rkp->rk_value, key, section); - return (errno); } - return (0); + +out: + mutex_unlock(&rcfile_mutex); + return (err); } /* @@ -510,139 +583,136 @@ rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value) struct rcsection *rsp; struct rckey *rkp; char *p; + int err; + + mutex_lock(&rcfile_mutex); rsp = rc_findsect(rcp, section); - if (!rsp) - return (ENOENT); + if (!rsp) { + err = ENOENT; + goto out; + } rkp = rc_sect_findkey(rsp, key); - if (!rkp) - return (ENOENT); + if (!rkp) { + err = ENOENT; + goto out; + } p = rkp->rk_value; while (*p && isspace(*p)) p++; if (*p == '0' || strcasecmp(p, "no") == 0 || strcasecmp(p, "false") == 0) { *value = 0; - return (0); + err = 0; + goto out; } if (*p == '1' || strcasecmp(p, "yes") == 0 || strcasecmp(p, "true") == 0) { *value = 1; - return (0); + err = 0; + goto out; } fprintf(stderr, dgettext(TEXT_DOMAIN, "invalid boolean value '%s' for key '%s' in section '%s' \n"), p, key, section); - return (EINVAL); + err = EINVAL; + +out: + mutex_unlock(&rcfile_mutex); + return (err); +} + +#ifdef DEBUG +void +dump_props(char *where) +{ + struct rcsection *rsp = NULL; + struct rckey *rkp = NULL; + + fprintf(stderr, "Settings %s\n", where); + SLIST_FOREACH(rsp, &smb_rc->rf_sect, rs_next) { + fprintf(stderr, "section=%s\n", rsp->rs_name); + fflush(stderr); + + SLIST_FOREACH(rkp, &rsp->rs_keys, rk_next) { + fprintf(stderr, " key=%s, value=%s\n", + rkp->rk_name, rkp->rk_value); + fflush(stderr); + } + } } +#endif /* - * Unified command line/rc file parser + * first parse "sharectl get smbfs, then $HOME/.nsmbrc + * This is called by library consumers (commands) */ int -opt_args_parse(struct rcfile *rcp, struct opt_args *ap, const char *sect, - opt_callback_t *callback) +smb_open_rcfile(char *home) { - int len, error; - - for (; ap->opt; ap++) { - switch (ap->type) { - case OPTARG_STR: - if (rc_getstringptr(rcp, sect, ap->name, &ap->str) != 0) - break; - len = strlen(ap->str); - if (len > ap->ival) { - fprintf(stdout, dgettext(TEXT_DOMAIN, - "rc: argument for option '%c' (%s) too long\n"), - ap->opt, ap->name); - return (EINVAL); - } - callback(ap); - break; - case OPTARG_BOOL: - error = rc_getbool(rcp, sect, ap->name, &ap->ival); - if (error == ENOENT) - break; - if (error) - return (EINVAL); - callback(ap); - break; - case OPTARG_INT: - if (rc_getint(rcp, sect, ap->name, &ap->ival) != 0) - break; - if (((ap->flag & OPTFL_HAVEMIN) && - ap->ival < ap->min) || - ((ap->flag & OPTFL_HAVEMAX) && - ap->ival > ap->max)) { - fprintf(stdout, dgettext(TEXT_DOMAIN, - "rc: argument for option '%c' (%s) " - "should be in [%d-%d] range\n"), - ap->opt, ap->name, ap->min, ap->max); - return (EINVAL); - } - callback(ap); - break; - default: - break; + char *fn; + int len, error = 0; + + mutex_lock(&rcfile_mutex); + + smb_rc = NULL; +#if 0 /* before SMF */ + fn = SMB_CFG_FILE; + error = rc_open(fn, &smb_rc); +#else + fn = SMBFS_SHARECTL_CMD; + error = rc_popen_cmd(fn, &smb_rc); +#endif + if (error != 0 && error != ENOENT) { + /* Error from fopen. strerror is OK. */ + fprintf(stderr, dgettext(TEXT_DOMAIN, + "Can't open %s: %s\n"), fn, strerror(errno)); + } +#ifdef DEBUG + if (smb_debug) + dump_props(fn); +#endif + + if (home) { + len = strlen(home) + 20; + fn = malloc(len); + snprintf(fn, len, "%s/.nsmbrc", home); + home_nsmbrc = 1; + error = rc_merge(fn, &smb_rc); + if (error != 0 && error != ENOENT) { + fprintf(stderr, dgettext(TEXT_DOMAIN, + "Can't open %s: %s\n"), fn, strerror(errno)); } + home_nsmbrc = 0; +#ifdef DEBUG + if (smb_debug) + dump_props(fn); +#endif + free(fn); } - return (0); + + /* Mostly ignore error returns above. */ + if (smb_rc == NULL) + error = ENOENT; + else + error = 0; + + mutex_unlock(&rcfile_mutex); + + return (error); } -int -opt_args_parseopt(struct opt_args *ap, int opt, char *arg, - opt_callback_t *callback) +/* + * This is called by library consumers (commands) + */ +void +smb_close_rcfile(void) { - int len; + struct rcfile *rcp; - for (; ap->opt; ap++) { - if (ap->opt != opt) - continue; - switch (ap->type) { - case OPTARG_STR: - ap->str = arg; - if (arg) { - len = strlen(ap->str); - if (len > ap->ival) { - fprintf(stdout, dgettext(TEXT_DOMAIN, - "opt: Argument for option '%c' (%s) too long\n"), - ap->opt, ap->name); - return (EINVAL); - } - callback(ap); - } - break; - case OPTARG_BOOL: - ap->ival = 0; - callback(ap); - break; - case OPTARG_INT: - errno = 0; - ap->ival = strtol(arg, NULL, 0); - if (errno) { - fprintf(stdout, dgettext(TEXT_DOMAIN, - "opt: Invalid integer value for " - "option '%c' (%s).\n"), - ap->opt, ap->name); - return (EINVAL); - } - if (((ap->flag & OPTFL_HAVEMIN) && - (ap->ival < ap->min)) || - ((ap->flag & OPTFL_HAVEMAX) && - (ap->ival > ap->max))) { - fprintf(stdout, dgettext(TEXT_DOMAIN, - "opt: Argument for option '%c' (%s) " - "should be in [%d-%d] range\n"), - ap->opt, ap->name, ap->min, ap->max); - return (EINVAL); - } - callback(ap); - break; - default: - break; - } - break; + if ((rcp = smb_rc) != NULL) { + smb_rc = NULL; + rc_close(rcp); } - return (0); } diff --git a/usr/src/lib/libsmbfs/smb/rcfile_priv.h b/usr/src/lib/libsmbfs/smb/rcfile_priv.h index 85ed97e1fd..ef9e31d7fc 100644 --- a/usr/src/lib/libsmbfs/smb/rcfile_priv.h +++ b/usr/src/lib/libsmbfs/smb/rcfile_priv.h @@ -1,4 +1,34 @@ -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * 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. + */ struct rckey { SLIST_ENTRY(rckey) rk_next; @@ -8,14 +38,17 @@ struct rckey { struct rcsection { SLIST_ENTRY(rcsection) rs_next; - SLIST_HEAD(rckey_head,rckey) rs_keys; + SLIST_HEAD(rckey_head, rckey) rs_keys; char *rs_name; }; - + struct rcfile { SLIST_ENTRY(rcfile) rf_next; SLIST_HEAD(rcsec_head, rcsection) rf_sect; char *rf_name; FILE *rf_f; + int rf_flags; /* RCFILE_... */ }; +#define RCFILE_HOME_NSMBRC 1 +#define RCFILE_IS_INSECURE 2 diff --git a/usr/src/lib/libsmbfs/smb/rq.c b/usr/src/lib/libsmbfs/smb/rq.c index 6390a3c157..7b21708428 100644 --- a/usr/src/lib/libsmbfs/smb/rq.c +++ b/usr/src/lib/libsmbfs/smb/rq.c @@ -47,26 +47,66 @@ #include <sysexits.h> #include <libintl.h> +#include <netsmb/smb.h> #include <netsmb/smb_lib.h> #include "private.h" +static uint32_t smb_map_doserr(uint8_t, uint16_t); +/* + * Create and initialize a request structure, for either an + * "internal" request (one that does not use the driver) or + * a regular "driver" request, that uses driver ioctls. + * + * The two kinds are built a little differently: + * Driver requests are composed starting with the + * first word of the "variable word vector" section. + * The driver prepends the SMB header and word count. + * The driver also needs an output buffer to receive + * the response, filled in via copyout in the ioctl. + * + * Internal requests are composed entirely in this library. + * Space for the SMB header is reserved here, and later + * filled in by smb_rq_internal before the send/receive. + */ int -smb_rq_init(struct smb_ctx *ctx, uchar_t cmd, size_t rpbufsz, - struct smb_rq **rqpp) +smb_rq_init(struct smb_ctx *ctx, uchar_t cmd, struct smb_rq **rqpp) { struct smb_rq *rqp; rqp = malloc(sizeof (*rqp)); if (rqp == NULL) - return (ENOMEM); + goto errout; bzero(rqp, sizeof (*rqp)); rqp->rq_cmd = cmd; rqp->rq_ctx = ctx; - mb_init(&rqp->rq_rq, M_MINSIZE); - mb_init(&rqp->rq_rp, rpbufsz); + + /* + * Setup the request buffer. + * Do the reply buffer later. + */ + if (mb_init(&rqp->rq_rq, M_MINSIZE)) + goto errout; + + /* Space for the SMB header. (filled in later) */ + mb_put_mem(&rqp->rq_rq, NULL, SMB_HDRLEN); + + /* + * Copy the ctx flags here, so the caller can + * update the req flags before the OTW call. + */ + rqp->rq_hflags = ctx->ct_hflags; + rqp->rq_hflags2 = ctx->ct_hflags2; + *rqpp = rqp; return (0); + +errout: + if (rqp) { + smb_rq_done(rqp); + free(rqp); + } + return (ENOMEM); } void @@ -77,81 +117,162 @@ smb_rq_done(struct smb_rq *rqp) free(rqp); } +/* + * Reserve space for the word count, which is filled in later by + * smb_rq_wend(). Also initialize the counter that it uses + * to figure out what value to fill in. + * + * Note that the word count happens to be 8-bits, + * which can lead to confusion. + */ void -smb_rq_wend(struct smb_rq *rqp) +smb_rq_wstart(struct smb_rq *rqp) { - if (rqp->rq_rq.mb_count & 1) - smb_error(dgettext(TEXT_DOMAIN, - "smbrq_wend: odd word count\n"), 0); - rqp->rq_wcount = rqp->rq_rq.mb_count / 2; - rqp->rq_rq.mb_count = 0; + struct mbdata *mbp = &rqp->rq_rq; + + mb_fit(mbp, 1, &rqp->rq_wcntp); + rqp->rq_wcbase = mbp->mb_count; } -int -smb_rq_dmem(struct mbdata *mbp, const char *src, size_t size) +/* + * Fill in the word count, in the space reserved by + * smb_rq_wstart(). + */ +void +smb_rq_wend(struct smb_rq *rqp) { - struct mbuf *m; - char *dst; - int cplen, error; + struct mbdata *mbp = &rqp->rq_rq; + int wcnt; - if (size == 0) - return (0); - m = mbp->mb_cur; - if ((error = m_getm(m, size, &m)) != 0) - return (error); - while (size > 0) { - cplen = M_TRAILINGSPACE(m); - if (cplen == 0) { - m = m->m_next; - continue; - } - if (cplen > (int)size) - cplen = size; - dst = mtod(m, char *) + m->m_len; - nls_mem_toext(dst, src, cplen); - size -= cplen; - src += cplen; - m->m_len += cplen; - mbp->mb_count += cplen; + if (rqp->rq_wcntp == NULL) { + DPRINT("no wcount ptr\n"); + return; } - mbp->mb_pos = mtod(m, char *) + m->m_len; - mbp->mb_cur = m; - return (0); + wcnt = mbp->mb_count - rqp->rq_wcbase; + if (wcnt > 0x1ff) + DPRINT("word count too large (%d)\n", wcnt); + if (wcnt & 1) + DPRINT("odd word count\n"); + wcnt >>= 1; + + /* + * Fill in the word count (8-bits). + * Also store it in the rq, in case + * we're using the ioctl path. + */ + *rqp->rq_wcntp = (char)wcnt; } -int -smb_rq_dstring(struct mbdata *mbp, const char *s) +/* + * Reserve space for the byte count, which is filled in later by + * smb_rq_bend(). Also initialize the counter that it uses + * to figure out what value to fill in. + * + * Note that the byte count happens to be 16-bits, + * which can lead to confusion. + */ +void +smb_rq_bstart(struct smb_rq *rqp) +{ + struct mbdata *mbp = &rqp->rq_rq; + + mb_fit(mbp, 2, &rqp->rq_bcntp); + rqp->rq_bcbase = mbp->mb_count; +} + +/* + * Fill in the byte count, in the space reserved by + * smb_rq_bstart(). + */ +void +smb_rq_bend(struct smb_rq *rqp) { - return (smb_rq_dmem(mbp, s, strlen(s) + 1)); + struct mbdata *mbp = &rqp->rq_rq; + int bcnt; + + if (rqp->rq_bcntp == NULL) { + DPRINT("no bcount ptr\n"); + return; + } + bcnt = mbp->mb_count - rqp->rq_bcbase; + if (bcnt > 0xffff) + DPRINT("byte count too large (%d)\n", bcnt); + /* + * Fill in the byte count (16-bits). + * Also store it in the rq, in case + * we're using the ioctl path. + * + * The pointer is char * type due to + * typical off-by-one alignment. + */ + rqp->rq_bcntp[0] = bcnt & 0xFF; + rqp->rq_bcntp[1] = (bcnt >> 8); } +/* + * Removed: smb_rq_dmem + * which was mostly like: mb_put_mem + */ + int smb_rq_simple(struct smb_rq *rqp) { struct smbioc_rq krq; struct mbdata *mbp; char *data; - int i; + uint32_t len; + size_t rpbufsz; + + bzero(&krq, sizeof (krq)); + krq.ioc_cmd = rqp->rq_cmd; + /* + * Make the SMB request body contiguous, + * and fill in the ioctl request. + */ mbp = smb_rq_getrequest(rqp); m_lineup(mbp->mb_top, &mbp->mb_top); data = mtod(mbp->mb_top, char *); - bzero(&krq, sizeof (krq)); - krq.ioc_cmd = rqp->rq_cmd; - krq.ioc_twc = rqp->rq_wcount; - krq.ioc_twords = data; - krq.ioc_tbc = mbp->mb_count; - krq.ioc_tbytes = data + rqp->rq_wcount * 2; + len = m_totlen(mbp->mb_top); + /* + * _rq_init left space for the SMB header, + * which makes mb_count the offset from + * the beginning of the header (useful). + * However, in this code path the driver + * prepends the header, so we skip it. + */ + krq.ioc_tbufsz = len - SMB_HDRLEN; + krq.ioc_tbuf = data + SMB_HDRLEN; + + /* + * Setup a buffer to hold the reply. + * + * Default size is M_MINSIZE, but the + * caller may increase rq_rpbufsz + * before calling this. + */ mbp = smb_rq_getreply(rqp); - krq.ioc_rpbufsz = mbp->mb_top->m_maxlen; - krq.ioc_rpbuf = mtod(mbp->mb_top, char *); - if (ioctl(rqp->rq_ctx->ct_fd, SMBIOC_REQUEST, &krq) == -1) { + rpbufsz = rqp->rq_rpbufsz; + if (rpbufsz < M_MINSIZE) + rpbufsz = M_MINSIZE; + if (mb_init(mbp, rpbufsz)) + return (ENOMEM); + krq.ioc_rbufsz = rpbufsz; + krq.ioc_rbuf = mtod(mbp->mb_top, char *); + + /* + * Call the driver + */ + if (ioctl(rqp->rq_ctx->ct_dev_fd, SMBIOC_REQUEST, &krq) == -1) return (errno); - } - mbp->mb_top->m_len = krq.ioc_rwc * 2 + krq.ioc_rbc; - rqp->rq_wcount = krq.ioc_rwc; - rqp->rq_bcount = krq.ioc_rbc; + + /* + * Initialize returned mbdata. + * SMB header already parsed. + */ + mbp->mb_top->m_len = krq.ioc_rbufsz; + return (0); } @@ -167,13 +288,11 @@ smb_t2_request(struct smb_ctx *ctx, int setupcount, uint16_t *setup, { smbioc_t2rq_t *krq; int i; - char *pass; - krq = (smbioc_t2rq_t *)malloc(sizeof (smbioc_t2rq_t)); bzero(krq, sizeof (*krq)); - if (setupcount < 0 || setupcount >= SMB_MAXSETUPWORDS) { + if (setupcount < 0 || setupcount >= SMBIOC_T2RQ_MAXSETUP) { /* Bogus setup count, or too many setup words */ return (EINVAL); } @@ -191,7 +310,7 @@ smb_t2_request(struct smb_ctx *ctx, int setupcount, uint16_t *setup, krq->ioc_rparam = rparam; krq->ioc_rdata = rdata; - if (ioctl(ctx->ct_fd, SMBIOC_T2RQ, krq) == -1) { + if (ioctl(ctx->ct_dev_fd, SMBIOC_T2RQ, krq) == -1) { return (errno); } @@ -200,5 +319,149 @@ smb_t2_request(struct smb_ctx *ctx, int setupcount, uint16_t *setup, *buffer_oflow = (krq->ioc_rpflags2 & SMB_FLAGS2_ERR_STATUS) && (krq->ioc_error == NT_STATUS_BUFFER_OVERFLOW); free(krq); + return (0); } + + +/* + * Do an over-the-wire call without using the nsmb driver. + * This is all "internal" to this library, and used only + * for connection setup (negotiate protocol, etc.) + */ +int +smb_rq_internal(struct smb_ctx *ctx, struct smb_rq *rqp) +{ + static const uint8_t ffsmb[4] = SMB_SIGNATURE; + struct smb_iods *is = &ctx->ct_iods; + uint32_t sigbuf[2]; + struct mbdata mbtmp, *mbp; + int err, save_mlen; + uint8_t ctmp; + + rqp->rq_uid = is->is_smbuid; + rqp->rq_tid = SMB_TID_UNKNOWN; + rqp->rq_mid = is->is_next_mid++; + + /* + * Fill in the NBT and SMB headers + * Using mbtmp so we can rewind without + * affecting the passed request mbdata. + */ + bcopy(&rqp->rq_rq, &mbtmp, sizeof (mbtmp)); + mbp = &mbtmp; + mbp->mb_cur = mbp->mb_top; + mbp->mb_pos = mbp->mb_cur->m_data; + mbp->mb_count = 0; + /* Have to save and restore m_len */ + save_mlen = mbp->mb_cur->m_len; + mbp->mb_cur->m_len = 0; + + /* + * rewind done; fill it in + */ + mb_put_mem(mbp, (char *)SMB_SIGNATURE, SMB_SIGLEN); + mb_put_uint8(mbp, rqp->rq_cmd); + mb_put_mem(mbp, NULL, 4); /* status */ + mb_put_uint8(mbp, rqp->rq_hflags); + mb_put_uint16le(mbp, rqp->rq_hflags2); + mb_put_uint16le(mbp, 0); /* pid_hi */ + mb_put_mem(mbp, NULL, 8); /* signature */ + mb_put_uint16le(mbp, 0); /* reserved */ + mb_put_uint16le(mbp, rqp->rq_tid); + mb_put_uint16le(mbp, 0); /* pid_lo */ + mb_put_uint16le(mbp, rqp->rq_uid); + mb_put_uint16le(mbp, rqp->rq_mid); + + /* Restore original m_len */ + mbp->mb_cur->m_len = save_mlen; + + /* + * Sign the message, if flags2 indicates. + */ + if (rqp->rq_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { + smb_rq_sign(rqp); + } + + /* + * Send it, wait for the reply. + */ + if ((err = smb_ssn_send(ctx, &rqp->rq_rq)) != 0) + return (err); + + if ((err = smb_ssn_recv(ctx, &rqp->rq_rp)) != 0) + return (err); + + /* + * Should have an SMB header, at least. + */ + mbp = &rqp->rq_rp; + if (mbp->mb_cur->m_len < SMB_HDRLEN) { + DPRINT("len < 32"); + return (EBADRPC); + } + + /* + * If the request was signed, validate the + * signature on the response. + */ + if (rqp->rq_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { + err = smb_rq_verify(rqp); + if (err) { + DPRINT("bad signature"); + return (err); + } + } + + /* + * Decode the SMB header. + */ + mb_get_mem(mbp, (char *)sigbuf, 4); + if (0 != bcmp(sigbuf, ffsmb, 4)) { + DPRINT("not SMB"); + return (EBADRPC); + } + mb_get_uint8(mbp, &ctmp); /* SMB cmd */ + mb_get_uint32le(mbp, &rqp->rq_status); + mb_get_uint8(mbp, &rqp->rq_hflags); + mb_get_uint16le(mbp, &rqp->rq_hflags2); + mb_get_uint16le(mbp, NULL); /* pid_hi */ + mb_get_mem(mbp, NULL, 8); /* signature */ + mb_get_uint16le(mbp, NULL); /* reserved */ + mb_get_uint16le(mbp, &rqp->rq_tid); + mb_get_uint16le(mbp, NULL); /* pid_lo */ + mb_get_uint16le(mbp, &rqp->rq_uid); + mb_get_uint16le(mbp, &rqp->rq_mid); + + /* + * Figure out the status return. + * Caller looks at rq_status. + */ + if ((rqp->rq_hflags2 & SMB_FLAGS2_ERR_STATUS) == 0) { + uint16_t serr; + uint8_t class; + + class = rqp->rq_status & 0xff; + serr = rqp->rq_status >> 16; + rqp->rq_status = smb_map_doserr(class, serr); + } + + return (0); +} + +/* + * Map old DOS errors (etc.) to NT status codes. + * We probably don't need this anymore, since + * the oldest server we talk to is NT. But if + * later find we do need this, add support here + * for the DOS errors we care about. + */ +static uint32_t +smb_map_doserr(uint8_t class, uint16_t serr) +{ + if (class == 0 && serr == 0) + return (0); + + DPRINT("class 0x%x serr 0x%x", (int)class, (int)serr); + return (NT_STATUS_UNSUCCESSFUL); +} diff --git a/usr/src/lib/libsmbfs/smb/signing.c b/usr/src/lib/libsmbfs/smb/signing.c new file mode 100644 index 0000000000..ef9a32e2e0 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/signing.c @@ -0,0 +1,265 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Signing support, using libmd + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> + +#include <sys/types.h> +#include <sys/md5.h> + +#include <netsmb/mchain.h> +#include <netsmb/smb.h> +#include <netsmb/smb_lib.h> + +#include "private.h" + +#define SMBSIGOFF 14 /* SMB signature offset */ +#define SMBSIGLEN 8 /* SMB signature length */ + +/* + * Set this to a small number to debug sequence numbers + * that seem to get out of step. + */ +#ifdef DEBUG +int nsmb_signing_fudge = 4; +#endif + +/* + * Compute MD5 digest of packet data, using the stored MAC key. + * + * See similar code in the driver: + * uts/common/fs/smbclnt/netsmb/smb_signing.c + * and on the server side: + * uts/common/fs/smbsrv/smb_signing.c + */ +static int +smb_compute_MAC(struct smb_ctx *ctx, mbuf_t *m, + uint32_t seqno, uchar_t *signature) +{ + MD5_CTX md5; + uchar_t digest[MD5_DIGEST_LENGTH]; + + /* + * This union is a little bit of trickery to: + * (1) get the sequence number int aligned, and + * (2) reduce the number of digest calls, at the + * cost of a copying 32 bytes instead of 8. + * Both sides of this union are 2+32 bytes. + */ + union { + struct { + uint8_t skip[2]; /* not used - just alignment */ + uint8_t raw[SMB_HDRLEN]; /* header length (32) */ + } r; + struct { + uint8_t skip[2]; /* not used - just alignment */ + uint8_t hdr[SMBSIGOFF]; /* sig. offset (14) */ + uint32_t sig[2]; /* MAC signature, aligned! */ + uint16_t ids[5]; /* pad, Tid, Pid, Uid, Mid */ + } s; + } smbhdr; + + if (m->m_len < SMB_HDRLEN) + return (EIO); + if (ctx->ct_mackey == NULL) + return (EINVAL); + + /* + * Make an aligned copy of the SMB header + * and fill in the sequence number. + */ + bcopy(m->m_data, smbhdr.r.raw, SMB_HDRLEN); + smbhdr.s.sig[0] = htolel(seqno); + smbhdr.s.sig[1] = 0; + + /* + * Compute the MAC: MD5(concat(Key, message)) + */ + MD5Init(&md5); + + /* Digest the MAC Key */ + MD5Update(&md5, ctx->ct_mackey, ctx->ct_mackeylen); + + /* Digest the (copied) SMB header */ + MD5Update(&md5, smbhdr.r.raw, SMB_HDRLEN); + + /* Digest the rest of the first mbuf */ + if (m->m_len > SMB_HDRLEN) { + MD5Update(&md5, m->m_data + SMB_HDRLEN, + m->m_len - SMB_HDRLEN); + } + m = m->m_next; + + /* Digest rest of the SMB message. */ + while (m) { + MD5Update(&md5, m->m_data, m->m_len); + m = m->m_next; + } + + /* Final */ + MD5Final(digest, &md5); + + /* + * Finally, store the signature. + * (first 8 bytes of the digest) + */ + if (signature) + bcopy(digest, signature, SMBSIGLEN); + + return (0); +} + +/* + * Sign a request with HMAC-MD5. + */ +int +smb_rq_sign(struct smb_rq *rqp) +{ + struct smb_ctx *ctx = rqp->rq_ctx; + mbuf_t *m = rqp->rq_rq.mb_top; + uint8_t *sigloc; + int err; + + /* + * Our mblk allocation ensures this, + * but just in case... + */ + if (m->m_len < SMB_HDRLEN) + return (EIO); + sigloc = (uchar_t *)m->m_data + SMBSIGOFF; + + if (ctx->ct_mackey == NULL) { + /* + * Signing is required, but we have no key yet + * fill in with the magic fake signing value. + * This happens with SPNEGO, NTLMSSP, ... + */ + bcopy("BSRSPLY", sigloc, 8); + return (0); + } + + /* + * This will compute the MAC and store it + * directly into the message at sigloc. + */ + rqp->rq_seqno = ctx->ct_mac_seqno; + ctx->ct_mac_seqno += 2; + err = smb_compute_MAC(ctx, m, rqp->rq_seqno, sigloc); + if (err) { + DPRINT("compute MAC, err %d", err); + bzero(sigloc, SMBSIGLEN); + return (ENOTSUP); + } + return (0); +} + +/* + * Verify reply signature. + */ +int +smb_rq_verify(struct smb_rq *rqp) +{ + struct smb_ctx *ctx = rqp->rq_ctx; + mbuf_t *m = rqp->rq_rp.mb_top; + uint8_t sigbuf[SMBSIGLEN]; + uint8_t *sigloc; + uint32_t rseqno; + int err, fudge; + + /* + * Note ct_mackey and ct_mackeylen gets initialized by + * smb_smb_ssnsetup. It's normal to have a null MAC key + * during extended security session setup. + */ + if (ctx->ct_mackey == NULL) + return (0); + + /* + * Let caller deal with empty reply or short messages by + * returning zero. Caller will fail later, in parsing. + */ + if (m == NULL) { + DPRINT("empty reply"); + return (0); + } + if (m->m_len < SMB_HDRLEN) { + DPRINT("short reply"); + return (0); + } + + sigloc = (uchar_t *)m->m_data + SMBSIGOFF; + rseqno = rqp->rq_seqno + 1; + + DPRINT("rq_rseqno = 0x%x", rseqno); + + err = smb_compute_MAC(ctx, m, rseqno, sigbuf); + if (err) { + DPRINT("compute MAC, err %d", err); + /* + * If we can't compute a MAC, then there's + * no point trying other seqno values. + */ + return (EBADRPC); + } + + /* + * Compare the computed signature with the + * one found in the message (at sigloc) + */ + if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0) + return (0); + + DPRINT("BAD signature, MID=0x%x", rqp->rq_mid); + +#ifdef DEBUG + /* + * For diag purposes, we check whether the client/server idea + * of the sequence # has gotten a bit out of sync. + */ + for (fudge = 1; fudge <= nsmb_signing_fudge; fudge++) { + smb_compute_MAC(ctx, m, rseqno + fudge, sigbuf); + if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0) + break; + smb_compute_MAC(ctx, m, rseqno - fudge, sigbuf); + if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0) { + fudge = -fudge; + break; + } + } + if (fudge <= nsmb_signing_fudge) { + DPRINT("rseqno=%d, but %d would have worked", + rseqno, rseqno + fudge); + } +#endif + return (EBADRPC); +} diff --git a/usr/src/lib/libsmbfs/smb/smb_crypt.h b/usr/src/lib/libsmbfs/smb/smb_crypt.h new file mode 100644 index 0000000000..15005ddab6 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/smb_crypt.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000-2001 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. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Various crypto stuff. + * from the driver: smb_crypt.c + */ + +int +smb_encrypt_DES(uchar_t *Result, int ResultLen, + const uchar_t *Key, int KeyLen, + const uchar_t *Data, int DataLen); + +int +smb_get_urandom(void *data, size_t dlen); diff --git a/usr/src/lib/libsmbfs/smb/spnegoparse.c b/usr/src/lib/libsmbfs/smb/spnegoparse.c index 5da1983c27..e9f1e2781b 100644 --- a/usr/src/lib/libsmbfs/smb/spnegoparse.c +++ b/usr/src/lib/libsmbfs/smb/spnegoparse.c @@ -20,8 +20,6 @@ // ///////////////////////////////////////////////////////////// -#pragma ident "%Z%%M% %I% %E% SMI" - #include <stdlib.h> #include <stdio.h> #include <memory.h> @@ -1615,7 +1613,7 @@ int ValidateMechList( unsigned char* pbMechListData, long nBoundaryLength ) int IsValidMechOid( SPNEGO_MECH_OID mechOid ) { return ( mechOid >= spnego_mech_oid_Kerberos_V5_Legacy && - mechOid <= spnego_mech_oid_Spnego ); + mechOid <= spnego_mech_oid_NTLMSSP ); } ///////////////////////////////////////////////////////////////////////////// diff --git a/usr/src/lib/libsmbfs/smb/ssnsetup.c b/usr/src/lib/libsmbfs/smb/ssnsetup.c new file mode 100644 index 0000000000..e4b5ec4f20 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ssnsetup.c @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2000-2001 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. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * SMB Session Setup, and related. + * Copied from the driver: smb_smb.c + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/mchain.h> +#include <netsmb/netbios.h> +#include <netsmb/smb_dev.h> +#include <netsmb/smb.h> + +#include <netsmb/smb_lib.h> +#include <netsmb/nb_lib.h> + +#include "private.h" +#include "charsets.h" +#include "ntlm.h" +#include "smb_crypt.h" + +/* + * When we have a _real_ ntstatus.h, eliminate this. + * XXX: Current smb.h defines it without the high bits. + */ +#define STATUS_MORE_PROCESSING_REQUIRED 0xC0000016 + +static int +smb__ssnsetup(struct smb_ctx *ctx, + struct mbdata *mbc1, struct mbdata *mbc2, + uint32_t *statusp, uint16_t *actionp); + +/* + * Session Setup: NULL session (anonymous) + */ +int +smb_ssnsetup_null(struct smb_ctx *ctx) +{ + int err; + uint32_t ntstatus; + uint16_t action = 0; + + if (ctx->ct_sopt.sv_caps & SMB_CAP_EXT_SECURITY) { + /* Should not get here with... */ + err = EINVAL; + goto out; + } + + err = smb__ssnsetup(ctx, NULL, NULL, &ntstatus, &action); + if (err) + goto out; + + DPRINT("status 0x%x action 0x%x", ntstatus, (int)action); + if (ntstatus != 0) + err = EAUTH; + +out: + return (err); +} + + +/* + * SMB Session Setup, using NTLMv1 (and maybe LMv1) + */ +int +smb_ssnsetup_ntlm1(struct smb_ctx *ctx) +{ + struct mbdata lm_mbc, nt_mbc; + int err; + uint32_t ntstatus; + uint16_t action = 0; + + if (ctx->ct_sopt.sv_caps & SMB_CAP_EXT_SECURITY) { + /* Should not get here with... */ + err = EINVAL; + goto out; + } + + /* Make mb_done calls at out safe. */ + bzero(&lm_mbc, sizeof (lm_mbc)); + bzero(&nt_mbc, sizeof (nt_mbc)); + + /* Put the LM,NTLM responses (as mbdata). */ + err = ntlm_put_v1_responses(ctx, &lm_mbc, &nt_mbc); + if (err) + goto out; + + /* + * If we negotiated signing, compute the MAC key + * and start signing messages, but only on the + * first non-null session login. + */ + if ((ctx->ct_vcflags & SMBV_WILL_SIGN) && + (ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) == 0) { + struct mbuf *m = nt_mbc.mb_top; + char *p; + + /* + * MAC_key = concat(session_key, nt_response) + */ + ctx->ct_mackeylen = NTLM_HASH_SZ + m->m_len; + ctx->ct_mackey = malloc(ctx->ct_mackeylen); + if (ctx->ct_mackey == NULL) { + ctx->ct_mackeylen = 0; + err = ENOMEM; + goto out; + } + p = ctx->ct_mackey; + memcpy(p, ctx->ct_ssn_key, NTLM_HASH_SZ); + memcpy(p + NTLM_HASH_SZ, m->m_data, m->m_len); + + /* OK, start signing! */ + ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE; + } + + err = smb__ssnsetup(ctx, &lm_mbc, &nt_mbc, &ntstatus, &action); + if (err) + goto out; + + DPRINT("status 0x%x action 0x%x", ntstatus, (int)action); + if (ntstatus != 0) + err = EAUTH; + +out: + mb_done(&lm_mbc); + mb_done(&nt_mbc); + + return (err); +} + +/* + * SMB Session Setup, using NTLMv2 (and LMv2) + */ +int +smb_ssnsetup_ntlm2(struct smb_ctx *ctx) +{ + struct mbdata lm_mbc, nt_mbc, ti_mbc; + int err; + uint32_t ntstatus; + uint16_t action = 0; + + if (ctx->ct_sopt.sv_caps & SMB_CAP_EXT_SECURITY) { + /* Should not get here with... */ + err = EINVAL; + goto out; + } + + /* Make mb_done calls at out safe. */ + bzero(&lm_mbc, sizeof (lm_mbc)); + bzero(&nt_mbc, sizeof (nt_mbc)); + bzero(&ti_mbc, sizeof (ti_mbc)); + + /* Build the NTLMv2 "target info" blob (as mbdata) */ + err = ntlm_build_target_info(ctx, NULL, &ti_mbc); + if (err) + goto out; + + /* Put the LMv2, NTLMv2 responses (as mbdata). */ + err = ntlm_put_v2_responses(ctx, &ti_mbc, &lm_mbc, &nt_mbc); + if (err) + goto out; + + /* + * If we negotiated signing, compute the MAC key + * and start signing messages, but only on the + * first non-null session login. + */ + if ((ctx->ct_vcflags & SMBV_WILL_SIGN) && + (ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) == 0) { + struct mbuf *m = nt_mbc.mb_top; + char *p; + + /* + * MAC_key = concat(session_key, nt_response) + */ + ctx->ct_mackeylen = NTLM_HASH_SZ + m->m_len; + ctx->ct_mackey = malloc(ctx->ct_mackeylen); + if (ctx->ct_mackey == NULL) { + ctx->ct_mackeylen = 0; + err = ENOMEM; + goto out; + } + p = ctx->ct_mackey; + memcpy(p, ctx->ct_ssn_key, NTLM_HASH_SZ); + memcpy(p + NTLM_HASH_SZ, m->m_data, m->m_len); + + /* OK, start signing! */ + ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE; + } + + err = smb__ssnsetup(ctx, &lm_mbc, &nt_mbc, &ntstatus, &action); + if (err) + goto out; + + DPRINT("status 0x%x action 0x%x", ntstatus, (int)action); + if (ntstatus != 0) + err = EAUTH; + +out: + mb_done(&ti_mbc); + mb_done(&lm_mbc); + mb_done(&nt_mbc); + + return (err); +} + +int +smb_ssnsetup_spnego(struct smb_ctx *ctx, struct mbdata *hint_mb) +{ + struct mbdata send_mb, recv_mb; + int err; + uint32_t ntstatus; + uint16_t action = 0; + + err = ssp_ctx_create_client(ctx, hint_mb); + if (err) + goto out; + + bzero(&send_mb, sizeof (send_mb)); + bzero(&recv_mb, sizeof (recv_mb)); + + /* NULL input indicates first call. */ + err = ssp_ctx_next_token(ctx, NULL, &send_mb); + if (err) + goto out; + + for (;;) { + err = smb__ssnsetup(ctx, &send_mb, &recv_mb, + &ntstatus, &action); + if (err) + goto out; + if (ntstatus == 0) + break; /* normal loop termination */ + if (ntstatus != STATUS_MORE_PROCESSING_REQUIRED) { + err = EAUTH; + break; + } + + /* middle calls get both in, out */ + err = ssp_ctx_next_token(ctx, &recv_mb, &send_mb); + if (err) + goto out; + } + DPRINT("status 0x%x action 0x%x", ntstatus, (int)action); + + /* NULL output indicates last call. */ + (void) ssp_ctx_next_token(ctx, &recv_mb, NULL); + +out: + ssp_ctx_destroy(ctx); + + return (err); +} + +/* + * Session Setup function used for all the forms we support. + * To allow this sharing, the crypto stuff is computed by + * callers and passed in as mbdata chains. Also, the args + * have different meanings for extended security vs. old. + * Some may be used as either IN or OUT parameters. + * + * For NTLM (v1, v2), all parameters are inputs + * mbc1: [in] LM password hash + * mbc2: [in] NT password hash + * For Extended security (spnego) + * mbc1: [in] outgoing blob data + * mbc2: [out] received blob data + * For both forms, these are optional: + * statusp: [out] NT status + * actionp: [out] Logon Action (i.e. SMB_ACT_GUEST) + */ +static int +smb__ssnsetup(struct smb_ctx *ctx, + struct mbdata *mbc1, struct mbdata *mbc2, + uint32_t *statusp, uint16_t *actionp) +{ + static const char NativeOS[] = "Solaris"; + static const char LanMan[] = "NETSMB"; + struct smb_sopt *sv = &ctx->ct_sopt; + struct smb_iods *is = &ctx->ct_iods; + struct smb_rq *rqp = NULL; + struct mbdata *mbp; + struct mbuf *m; + int err, uc; + uint32_t caps; + uint16_t bc, len1, len2, sblen; + uint8_t wc; + + /* + * Some of the "capability" bits we offer will be copied + * from those offered by the server, with a mask applied. + * This is the mask of capabilies copied from the server. + * Some others get special handling below. + */ + static const uint32_t caps_mask = + SMB_CAP_UNICODE | + SMB_CAP_LARGE_FILES | + SMB_CAP_NT_SMBS | + SMB_CAP_STATUS32 | + SMB_CAP_EXT_SECURITY; + + caps = ctx->ct_sopt.sv_caps & caps_mask; + uc = ctx->ct_hflags2 & SMB_FLAGS2_UNICODE; + + err = smb_rq_init(ctx, SMB_COM_SESSION_SETUP_ANDX, &rqp); + if (err) + goto out; + + /* + * Build the SMB request. + */ + mbp = &rqp->rq_rq; + smb_rq_wstart(rqp); + mb_put_uint16le(mbp, 0xff); /* 0: AndXCommand */ + mb_put_uint16le(mbp, 0); /* 1: AndXOffset */ + mb_put_uint16le(mbp, sv->sv_maxtx); /* 2: MaxBufferSize */ + mb_put_uint16le(mbp, sv->sv_maxmux); /* 3: MaxMpxCount */ + mb_put_uint16le(mbp, 1); /* 4: VcNumber */ + mb_put_uint32le(mbp, sv->sv_skey); /* 5,6: Session Key */ + + if (caps & SMB_CAP_EXT_SECURITY) { + len1 = mbc1 ? mbc1->mb_count : 0; + mb_put_uint16le(mbp, len1); /* 7: Sec. Blob Len */ + mb_put_uint32le(mbp, 0); /* 8,9: reserved */ + mb_put_uint32le(mbp, caps); /* 10,11: Capabilities */ + smb_rq_wend(rqp); /* 12: Byte Count */ + smb_rq_bstart(rqp); + if (mbc1 && mbc1->mb_top) { + mb_put_mbuf(mbp, mbc1->mb_top); /* sec. blob */ + mbc1->mb_top = NULL; /* consumed */ + } + /* mbc2 is required below */ + if (mbc2 == NULL) { + err = EINVAL; + goto out; + } + } else { + len1 = mbc1 ? mbc1->mb_count : 0; + len2 = mbc2 ? mbc2->mb_count : 0; + mb_put_uint16le(mbp, len1); /* 7: LM pass. len */ + mb_put_uint16le(mbp, len2); /* 8: NT pass. len */ + mb_put_uint32le(mbp, 0); /* 9,10: reserved */ + mb_put_uint32le(mbp, caps); /* 11,12: Capabilities */ + smb_rq_wend(rqp); /* 13: Byte Count */ + smb_rq_bstart(rqp); + if (mbc1 && mbc1->mb_top) { + mb_put_mbuf(mbp, mbc1->mb_top); /* LM password */ + mbc1->mb_top = NULL; /* consumed */ + } + if (mbc2 && mbc2->mb_top) { + mb_put_mbuf(mbp, mbc2->mb_top); /* NT password */ + mbc2->mb_top = NULL; /* consumed */ + } + mb_put_dstring(mbp, ctx->ct_user, uc); + mb_put_dstring(mbp, ctx->ct_domain, uc); + } + mb_put_dstring(mbp, NativeOS, uc); + mb_put_dstring(mbp, LanMan, uc); + smb_rq_bend(rqp); + + err = smb_rq_internal(ctx, rqp); + if (err) + goto out; + + if (statusp) + *statusp = rqp->rq_status; + + /* + * If we have a real error, the response probably has + * no more data, so don't try to parse any more. + * Note: err=0, means rq_status is valid. + */ + if (rqp->rq_status != 0 && + rqp->rq_status != STATUS_MORE_PROCESSING_REQUIRED) { + goto out; + } + + /* + * Parse the reply + */ + uc = rqp->rq_hflags2 & SMB_FLAGS2_UNICODE; + is->is_smbuid = rqp->rq_uid; + mbp = &rqp->rq_rp; + + err = mb_get_uint8(mbp, &wc); + if (err) + goto out; + + err = EBADRPC; /* for any problems in this section */ + if (caps & SMB_CAP_EXT_SECURITY) { + if (wc != 4) + goto out; + mb_get_uint16le(mbp, NULL); /* secondary cmd */ + mb_get_uint16le(mbp, NULL); /* andxoffset */ + mb_get_uint16le(mbp, actionp); /* action */ + mb_get_uint16le(mbp, &sblen); /* sec. blob len */ + mb_get_uint16le(mbp, &bc); /* byte count */ + /* + * Get the security blob, after + * sanity-checking the length. + */ + if (sblen == 0 || bc < sblen) + goto out; + err = mb_get_mbuf(mbp, sblen, &m); + if (err) + goto out; + mb_initm(mbc2, m); + mbc2->mb_count = sblen; + } else { + if (wc != 3) + goto out; + mb_get_uint16le(mbp, NULL); /* secondary cmd */ + mb_get_uint16le(mbp, NULL); /* andxoffset */ + mb_get_uint16le(mbp, actionp); /* action */ + err = mb_get_uint16le(mbp, &bc); /* byte count */ + if (err) + goto out; + } + + /* + * Native OS, LANMGR, & Domain follow here. + * Parse these strings and store for later. + * If unicode, they should be aligned. + * + * Note that with Extended security, we may use + * multiple calls to this function. Only parse + * these strings on the last one (status == 0). + * Ditto for the CAP_LARGE work-around. + */ + if (rqp->rq_status != 0) + goto out; + + /* Ignore any parsing errors for these strings. */ + err = mb_get_string(mbp, &ctx->ct_srv_OS, uc); + DPRINT("server OS: %s", err ? "?" : ctx->ct_srv_OS); + err = mb_get_string(mbp, &ctx->ct_srv_LM, uc); + DPRINT("server LM: %s", err ? "?" : ctx->ct_srv_LM); + /* + * There's sometimes a server domain folloing + * at this point, but we don't need it. + */ + + /* Success! (See Ignore any ... above) */ + err = 0; + + /* + * Windows systems don't suport CAP_LARGE_READX,WRITEX + * when signing is enabled, so adjust sv_caps. + */ + if (ctx->ct_srv_OS && + 0 == strncmp(ctx->ct_srv_OS, "Windows ", 8)) { + DPRINT("Server is Windows"); + if (ctx->ct_vcflags & SMBV_WILL_SIGN) { + DPRINT("disable CAP_LARGE_(r/w)"); + ctx->ct_sopt.sv_caps &= + ~(SMB_CAP_LARGE_READX | SMB_CAP_LARGE_WRITEX); + } + } + +out: + if (rqp) + smb_rq_done(rqp); + + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/ssp.c b/usr/src/lib/libsmbfs/smb/ssp.c new file mode 100644 index 0000000000..f8433ba8e5 --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ssp.c @@ -0,0 +1,395 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Security Provider glue + * + * Modeled after SSPI for now, only because we're currently + * using the Microsoft sample spnego code. + * + * ToDo: Port all of this to GSS-API plugins. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <strings.h> +#include <netdb.h> +#include <libintl.h> +#include <xti.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/byteorder.h> +#include <sys/socket.h> +#include <sys/fcntl.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <netsmb/smb_lib.h> +#include <netsmb/mchain.h> + +#include "private.h" +#include "charsets.h" +#include "spnego.h" +#include "derparse.h" +#include "ssp.h" + + +/* + * ssp_ctx_create_client + * + * This is the first function called for SMB "extended security". + * Here we select a security support provider (SSP), or mechanism, + * and build the security context used throughout authentication. + * + * Note that we receive a "hint" in the SMB Negotiate response + * that contains the list of mechanisms supported by the server. + * We use this to help us select a mechanism. + * + * With SSPI this would call: + * ssp->InitSecurityInterface() + * ssp->AcquireCredentialsHandle() + * ssp->InitializeSecurityContext() + * With GSS-API this will become: + * gss_import_name(... service_principal_name) + * gss_init_sec_context(), etc. + */ +int +ssp_ctx_create_client(struct smb_ctx *ctx, struct mbdata *hint_mb) +{ + struct ssp_ctx *sp; + mbuf_t *m; + SPNEGO_MECH_OID oid; + int indx, rc; + int err = ENOTSUP; /* in case nothing matches */ + + sp = malloc(sizeof (*sp)); + if (sp == NULL) + return (ENOMEM); + bzero(sp, sizeof (*sp)); + ctx->ct_ssp_ctx = sp; + sp->smb_ctx = ctx; + + /* + * Parse the SPNEGO "hint" to get the server's list of + * supported mechanisms. If the "hint" is empty, + * assume NTLMSSP. (Or could use "raw NTLMSSP") + */ + m = hint_mb->mb_top; + if (m == NULL) + goto use_ntlm; + rc = spnegoInitFromBinary((uchar_t *)m->m_data, m->m_len, + &sp->sp_hint); + if (rc) { + DPRINT("parse hint, rc %d", rc); + goto use_ntlm; + } + + /* + * Did the server offer Kerberos? + * Either spec. OID or legacy is OK, + * but have to remember what we got. + */ + oid = spnego_mech_oid_NotUsed; + if (0 == spnegoIsMechTypeAvailable(sp->sp_hint, + spnego_mech_oid_Kerberos_V5, &indx)) + oid = spnego_mech_oid_Kerberos_V5; + else if (0 == spnegoIsMechTypeAvailable(sp->sp_hint, + spnego_mech_oid_Kerberos_V5_Legacy, &indx)) + oid = spnego_mech_oid_Kerberos_V5_Legacy; + if (oid != spnego_mech_oid_NotUsed) { + /* + * Yes! Server offers Kerberos. + * Try to init our krb5 mechanism. + * It will fail if the calling user + * does not have krb5 credentials. + */ + sp->sp_mech = oid; + err = krb5ssp_init_client(sp); + if (err == 0) { + DPRINT("using Kerberos"); + return (0); + } + /* else fall back to NTLMSSP */ + } + + /* + * Did the server offer NTLMSSP? + */ + if (0 == spnegoIsMechTypeAvailable(sp->sp_hint, + spnego_mech_oid_NTLMSSP, &indx)) { + /* + * OK, we'll use NTLMSSP + */ + use_ntlm: + sp->sp_mech = spnego_mech_oid_NTLMSSP; + err = ntlmssp_init_client(sp); + if (err == 0) { + DPRINT("using NTLMSSP"); + return (0); + } + } + + /* No supported mechanisms! */ + return (err); +} + + +/* + * ssp_ctx_destroy + * + * Dispatch to the mechanism-specific destroy. + */ +void +ssp_ctx_destroy(struct smb_ctx *ctx) +{ + ssp_ctx_t *sp; + + sp = ctx->ct_ssp_ctx; + ctx->ct_ssp_ctx = NULL; + + if (sp == NULL) + return; + + if (sp->sp_destroy != NULL) + (sp->sp_destroy)(sp); + + if (sp->sp_hint != NULL) + spnegoFreeData(sp->sp_hint); + + free(sp); +} + + +/* + * ssp_ctx_next_token + * + * This is the function called to generate the next token to send, + * given a token just received, using the selected back-end method. + * The back-end method is called a security service provider (SSP). + * + * This is also called to generate the first token to send + * (when called with caller_in == NULL) and to handle the last + * token received (when called with caller_out == NULL). + * See caller: smb_ssnsetup_spnego + * + * Note that if the back-end SSP "next token" function ever + * returns an error, the conversation ends, and there are + * no further calls to this function for this context. + * + * General outline of this funcion: + * if (caller_in) + * Unwrap caller_in spnego blob, + * store payload in body_in + * Call back-end SSP "next token" method (body_in, body_out) + * if (caller_out) + * Wrap returned body_out in spnego, + * store in caller_out + * + * With SSPI this would call: + * ssp->InitializeSecurityContext() + * With GSS-API this will become: + * gss_init_sec_context() + */ +int +ssp_ctx_next_token(struct smb_ctx *ctx, + struct mbdata *caller_in, + struct mbdata *caller_out) +{ + struct mbdata body_in, body_out; + SPNEGO_TOKEN_HANDLE stok_in, stok_out; + SPNEGO_NEGRESULT result; + ssp_ctx_t *sp; + struct mbuf *m; + ulong_t toklen; + int err, rc; + + bzero(&body_in, sizeof (body_in)); + bzero(&body_out, sizeof (body_out)); + stok_out = stok_in = NULL; + sp = ctx->ct_ssp_ctx; + + /* + * If we have an spnego input token, parse it, + * extract the payload for the back-end SSP. + */ + if (caller_in != NULL) { + + /* + * Let the spnego code parse it. + */ + m = caller_in->mb_top; + rc = spnegoInitFromBinary((uchar_t *)m->m_data, + m->m_len, &stok_in); + if (rc) { + DPRINT("parse reply, rc %d", rc); + err = EBADRPC; + goto out; + } + /* Note: Allocated stok_in */ + + /* + * Now get the payload. Two calls: + * first gets the size, 2nd the data. + * + * Expect SPNEGO_E_BUFFER_TOO_SMALL here, + * but if the payload is missing, we'll + * get SPNEGO_E_ELEMENT_UNAVAILABLE. + */ + rc = spnegoGetMechToken(stok_in, NULL, &toklen); + switch (rc) { + case SPNEGO_E_ELEMENT_UNAVAILABLE: + toklen = 0; + break; + case SPNEGO_E_BUFFER_TOO_SMALL: + /* have toklen */ + break; + default: + DPRINT("GetMechTok1, rc %d", rc); + err = EBADRPC; + goto out; + } + err = mb_init(&body_in, (size_t)toklen); + if (err) + goto out; + m = body_in.mb_top; + if (toklen > 0) { + rc = spnegoGetMechToken(stok_in, + (uchar_t *)m->m_data, &toklen); + if (rc) { + DPRINT("GetMechTok2, rc %d", rc); + err = EBADRPC; + goto out; + } + body_in.mb_count = m->m_len = (size_t)toklen; + } + } + + /* + * Call the back-end security provider (SSP) to + * handle the received token (if present) and + * generate an output token (if requested). + */ + err = sp->sp_nexttok(sp, + caller_in ? &body_in : NULL, + caller_out ? &body_out : NULL); + if (err) + goto out; + + /* + * Wrap the outgoing body if requested, + * either negTokenInit on first call, or + * negTokenTarg on subsequent calls. + */ + if (caller_out != NULL) { + m = body_out.mb_top; + + if (caller_in == NULL) { + /* + * This is the first call, so create a + * negTokenInit. + */ + rc = spnegoCreateNegTokenInit( + sp->sp_mech, 0, + (uchar_t *)m->m_data, m->m_len, + NULL, 0, &stok_out); + /* Note: allocated stok_out */ + } else { + /* + * Note: must pass spnego_mech_oid_NotUsed, + * instead of sp->sp_mech so that the spnego + * code will not marshal a mech OID list. + * The mechanism is determined at this point, + * and some servers won't parse an unexpected + * mech. OID list in a negTokenTarg + */ + rc = spnegoCreateNegTokenTarg( + spnego_mech_oid_NotUsed, + spnego_negresult_NotUsed, + (uchar_t *)m->m_data, m->m_len, + NULL, 0, &stok_out); + /* Note: allocated stok_out */ + } + if (rc) { + DPRINT("CreateNegTokenX, rc 0x%x", rc); + err = EBADRPC; + goto out; + } + + /* + * Copy binary from stok_out to caller_out + * Two calls: get the size, get the data. + */ + rc = spnegoTokenGetBinary(stok_out, NULL, &toklen); + if (rc != SPNEGO_E_BUFFER_TOO_SMALL) { + DPRINT("GetBinary1, rc 0x%x", rc); + err = EBADRPC; + goto out; + } + err = mb_init(caller_out, (size_t)toklen); + if (err) + goto out; + m = caller_out->mb_top; + rc = spnegoTokenGetBinary(stok_out, + (uchar_t *)m->m_data, &toklen); + if (rc) { + DPRINT("GetBinary2, rc 0x%x", rc); + err = EBADRPC; + goto out; + } + caller_out->mb_count = m->m_len = (size_t)toklen; + } else { + /* + * caller_out == NULL, so this is the "final" call. + * Get final SPNEGO result from the INPUT token. + */ + rc = spnegoGetNegotiationResult(stok_in, &result); + if (rc) { + DPRINT("rc 0x%x", rc); + err = EBADRPC; + goto out; + } + DPRINT("spnego result: 0x%x", result); + if (result != spnego_negresult_success) { + err = EAUTH; + goto out; + } + } + err = 0; + +out: + mb_done(&body_in); + mb_done(&body_out); + spnegoFreeData(stok_in); + spnegoFreeData(stok_out); + + return (err); +} diff --git a/usr/src/lib/libsmbfs/smb/ssp.h b/usr/src/lib/libsmbfs/smb/ssp.h new file mode 100644 index 0000000000..7370fffc9c --- /dev/null +++ b/usr/src/lib/libsmbfs/smb/ssp.h @@ -0,0 +1,56 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SSP_H +#define _SSP_H + +/* + * Security Support Package (SSP) interface, + * somewhat modeled on Microsoft's SSPI. + * + * XXX: Yes, should use GSS-API. See ssp.c + */ + +typedef struct ssp_ctx { + struct smb_ctx *smb_ctx; + + SPNEGO_TOKEN_HANDLE sp_hint; + SPNEGO_MECH_OID sp_mech; + + /* + * Now the mechanism-specific stuff. + */ + int (*sp_nexttok)(struct ssp_ctx *, + struct mbdata *, struct mbdata *); + void (*sp_destroy)(struct ssp_ctx *); + void *sp_private; + +} ssp_ctx_t; + +int ntlmssp_init_client(ssp_ctx_t *); +int krb5ssp_init_client(ssp_ctx_t *); + +#endif /* _SSP_H */ diff --git a/usr/src/lib/libsmbfs/smb/subr.c b/usr/src/lib/libsmbfs/smb/subr.c index 36a3c30ed9..0b81606f2c 100644 --- a/usr/src/lib/libsmbfs/smb/subr.c +++ b/usr/src/lib/libsmbfs/smb/subr.c @@ -49,15 +49,13 @@ #include <netsmb/netbios.h> #include <netsmb/smb_lib.h> #include <netsmb/nb_lib.h> -#include <cflib.h> + #include <err.h> -uid_t real_uid, eff_uid; +#include "private.h" static int smblib_initialized; -struct rcfile *smb_rc; - int smb_lib_init(void) { @@ -74,6 +72,23 @@ smb_lib_init(void) return (0); } +int +smb_getlocalname(char **namepp) +{ + char buf[SMBIOC_MAX_NAME], *cp; + + if (gethostname(buf, sizeof (buf)) != 0) + return (errno); + cp = strchr(buf, '.'); + if (cp) + *cp = '\0'; + cp = strdup(buf); + if (cp == NULL) + return (ENOMEM); + *namepp = cp; + return (0); +} + /* * Private version of strerror(3C) that * knows our special error codes. @@ -162,93 +177,6 @@ smb_printb(char *dest, int flags, const struct smb_bitname *bnp) { return (dest); } -extern int home_nsmbrc; - -#ifdef DEBUG -#include "queue.h" -#include "rcfile_priv.h" - -struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname); -struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *keyname); - -void -dump_props(char *where) -{ - struct rcsection *rsp = NULL; - struct rckey *rkp = NULL; - - printf("Settings %s\n", where); - SLIST_FOREACH(rsp, &smb_rc->rf_sect, rs_next) { - printf("section=%s\n", rsp->rs_name); - fflush(stdout); - - SLIST_FOREACH(rkp, &rsp->rs_keys, rk_next) { - printf(" key=%s, value=%s\n", - rkp->rk_name, rkp->rk_value); - fflush(stdout); - } - } -} -#endif - -/* - * first read ~/.smbrc, next try to merge SMB_CFG_FILE - if that fails - * because SMB_CFG_FILE doesn't exist, try to merge OLD_SMB_CFG_FILE - */ -int -smb_open_rcfile(struct smb_ctx *ctx) -{ - char *home, *fn; - int error, len; - - smb_rc = NULL; -#ifdef DEPRECATED - fn = SMB_CFG_FILE; - error = rc_merge(fn, &smb_rc); - if (error == ENOENT) { - /* - * OK, try to read a config file in the old location. - */ - fn = OLD_SMB_CFG_FILE; - error = rc_merge(fn, &smb_rc); - } -#endif - fn = "/usr/sbin/sharectl get smbfs"; - error = rc_merge_pipe(fn, &smb_rc); - if (error != 0 && error != ENOENT) - fprintf(stderr, dgettext(TEXT_DOMAIN, - "Can't open %s: %s\n"), fn, smb_strerror(errno)); -#ifdef DEBUG - if (smb_debug) - dump_props("after reading global repository"); -#endif - - home = getenv("HOME"); - if (home == NULL && ctx && ctx->ct_home) - home = ctx->ct_home; - if (home) { - len = strlen(home) + 20; - fn = malloc(len); - snprintf(fn, len, "%s/.nsmbrc", home); - home_nsmbrc = 1; - error = rc_merge(fn, &smb_rc); - if (error != 0 && error != ENOENT) { - fprintf(stderr, dgettext(TEXT_DOMAIN, - "Can't open %s: %s\n"), fn, smb_strerror(errno)); - } - free(fn); - } - home_nsmbrc = 0; -#ifdef DEBUG - if (smb_debug) - dump_props("after reading user settings"); -#endif - if (smb_rc == NULL) { - return (ENOENT); - } - return (0); -} - void smb_simplecrypt(char *dst, const char *src) { @@ -303,6 +231,79 @@ smb_simpledecrypt(char *dst, const char *src) return (0); } +/* + * Number of seconds between 1970 and 1601 year + * (134774 * 24 * 60 * 60) + */ +static const uint64_t DIFF1970TO1601 = 11644473600ULL; + +void +smb_time_local2server(struct timeval *tsp, int tzoff, long *seconds) +{ + *seconds = tsp->tv_sec - tzoff * 60; +} + +void +smb_time_server2local(ulong_t seconds, int tzoff, struct timeval *tsp) +{ + tsp->tv_sec = seconds + tzoff * 60; + tsp->tv_usec = 0; +} + +/* + * Time from server comes as UTC, so no need to use tz + */ +/*ARGSUSED*/ +void +smb_time_NT2local(uint64_t nsec, int tzoff, struct timeval *tsp) +{ + smb_time_server2local(nsec / 10000000 - DIFF1970TO1601, 0, tsp); +} + +/*ARGSUSED*/ +void +smb_time_local2NT(struct timeval *tsp, int tzoff, uint64_t *nsec) +{ + long seconds; + + smb_time_local2server(tsp, 0, &seconds); + *nsec = (((uint64_t)(seconds) & ~1) + DIFF1970TO1601) * + (uint64_t)10000000; +} + +void +smb_hexdump(const void *buf, int len) +{ + const uchar_t *p = buf; + int ofs = 0; + + while (len--) { + if (ofs % 16 == 0) + fprintf(stderr, "%02X: ", ofs); + fprintf(stderr, "%02x ", *p++); + ofs++; + if (ofs % 16 == 0) + fprintf(stderr, "\n"); + } + if (ofs % 16 != 0) + fprintf(stderr, "\n"); +} + +void +dprint(const char *fname, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if (smb_debug) { + fprintf(stderr, "%s: ", fname); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } + va_end(ap); +} + #undef __progname char *__progname = NULL; diff --git a/usr/src/lib/libsmbfs/smb/ui-sun.c b/usr/src/lib/libsmbfs/smb/ui-sun.c index 7512d2c964..69aa3161ab 100644 --- a/usr/src/lib/libsmbfs/smb/ui-sun.c +++ b/usr/src/lib/libsmbfs/smb/ui-sun.c @@ -22,8 +22,6 @@ * @APPLE_LICENSE_HEADER_END@ */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * Routines for interacting with the user to get credentials * (workgroup/domain, username, password, etc.) @@ -38,11 +36,11 @@ #include <ctype.h> #include <netsmb/smb_lib.h> -#include <netsmb/smb_keychain.h> +#include "private.h" +#include "ntlm.h" +#if 0 /* not yet */ #define MAXLINE 127 -#define MAXPASSWD 256 /* from libc:getpass */ - static void smb_tty_prompt(char *prmpt, char *buf, size_t buflen) @@ -72,78 +70,69 @@ smb_tty_prompt(char *prmpt, /* Use input as new value. */ strncpy(buf, temp, buflen); } +#endif /* not yet */ +/* + * Prompt for a new password after auth. failure. + * (and maybe new user+domain, but not yet) + */ int -smb_get_authentication( - char *dom, size_t domlen, - char *usr, size_t usrlen, - char *passwd, size_t passwdlen, - const char *systemname, struct smb_ctx *ctx) +smb_get_authentication(struct smb_ctx *ctx) { char *npw; - int error, i, kcask, kcerr; + int err; - if (ctx->ct_flags & SMBCF_KCFOUND || ctx->ct_flags & SMBCF_KCBAD) { - ctx->ct_flags &= ~SMBCF_KCFOUND; - } else { - ctx->ct_flags &= ~(SMBCF_KCFOUND | SMBCF_KCDOMAIN); + /* + * If we're getting a password, we must be doing + * some kind of NTLM, possibly after a failure to + * authenticate using Kerberos. Turn off krb5. + */ + ctx->ct_authflags &= ~SMB_AT_KRB5; - /* - * 1st: try lookup using system name - */ - kcerr = smbfs_keychain_chk(systemname, usr); - if (!kcerr) { - /* - * Need passwd to be not empty for existing logic. - * The string here is arbitrary (a debugging hint) - * and will be replaced in the driver by the real - * password from the keychain. - */ - strcpy(passwd, "$KC_SYSTEM"); - ctx->ct_flags |= SMBCF_KCFOUND; - if (smb_debug) { - printf("found keychain entry for" - " server/user: %s/%s\n", - systemname, usr); - } - return (0); - } + if (ctx->ct_flags & SMBCF_KCFOUND) { + /* Tried a keychain hash and failed. */ + /* XXX: delete the KC entry? */ + ctx->ct_flags |= SMBCF_KCBAD; + } + + if (ctx->ct_flags & SMBCF_NOPWD) + return (ENOTTY); + + if (isatty(STDIN_FILENO)) { + + /* Need command-line prompting. */ + npw = getpassphrase(dgettext(TEXT_DOMAIN, "Password:")); + if (npw == NULL) + return (EINTR); + memset(ctx->ct_password, 0, sizeof (ctx->ct_password)); + strlcpy(ctx->ct_password, npw, sizeof (ctx->ct_password)); + } else { /* - * 2nd: try lookup using domain name + * XXX: Ask the user for help, possibly via + * GNOME dbus or some such... (todo). */ - kcerr = smbfs_keychain_chk(dom, usr); - if (!kcerr) { - /* Need passwd to be not empty... (see above) */ - strcpy(passwd, "$KC_DOMAIN"); - ctx->ct_flags |= (SMBCF_KCFOUND | SMBCF_KCDOMAIN); - if (smb_debug) { - printf("found keychain entry for" - " domain/user: %s/%s\n", - dom, usr); - } - return (0); - } - } - - if (isatty(STDIN_FILENO)) { /* need command-line prompting? */ - if (passwd && passwd[0] == '\0') { - npw = getpassphrase(dgettext(TEXT_DOMAIN, "Password:")); - strncpy(passwd, npw, passwdlen); - } - return (0); + smb_error(dgettext(TEXT_DOMAIN, + "Cannot prompt for a password when input is redirected."), 0); + return (ENOTTY); } /* - * XXX: Ask the user for help, possibly via - * GNOME dbus or some such... (todo). + * Recompute the password hashes. */ - smb_error(dgettext(TEXT_DOMAIN, - "Cannot prompt for a password when input is redirected."), 0); + if (ctx->ct_password[0]) { + err = ntlm_compute_lm_hash(ctx->ct_lmhash, ctx->ct_password); + if (err != 0) + return (err); + err = ntlm_compute_nt_hash(ctx->ct_nthash, ctx->ct_password); + if (err != 0) + return (err); + } - return (ENOTTY); + return (0); } +/*ARGSUSED*/ int smb_browse(struct smb_ctx *ctx, int anon) { diff --git a/usr/src/lib/libsmbfs/smb/utf_str.c b/usr/src/lib/libsmbfs/smb/utf_str.c index e80b1180f2..f53189e64c 100644 --- a/usr/src/lib/libsmbfs/smb/utf_str.c +++ b/usr/src/lib/libsmbfs/smb/utf_str.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -205,3 +205,67 @@ convert_utf8_to_ucs2xx(iconv_t cd, const char *utf8_string) return (obuf); } + + +/* + * A simple wrapper around u8_textprep_str() that returns the Unicode + * upper-case version of some string. Returns memory from malloc. + * Borrowed from idmapd. + */ +static char * +utf8_str_to_upper_or_lower(const char *s, int upper_lower) +{ + char *res = NULL; + char *outs; + size_t inlen, outlen, inbleft, outbleft; + int rc, err; + + /* + * u8_textprep_str() does not allocate memory. The input and + * output buffers may differ in size (though that would be more + * likely when normalization is done). We have to loop over it... + * + * To improve the chances that we can avoid looping we add 10 + * bytes of output buffer room the first go around. + */ + inlen = inbleft = strlen(s); + outlen = outbleft = inlen + 10; + if ((res = malloc(outlen)) == NULL) + return (NULL); + outs = res; + + while ((rc = u8_textprep_str((char *)s, &inbleft, outs, + &outbleft, upper_lower, U8_UNICODE_LATEST, &err)) < 0 && + err == E2BIG) { + if ((res = realloc(res, outlen + inbleft)) == NULL) + return (NULL); + /* adjust input/output buffer pointers */ + s += (inlen - inbleft); + outs = res + outlen - outbleft; + /* adjust outbleft and outlen */ + outlen += inbleft; + outbleft += inbleft; + } + + if (rc < 0) { + free(res); + res = NULL; + return (NULL); + } + + res[outlen - outbleft] = '\0'; + + return (res); +} + +char * +utf8_str_toupper(const char *s) +{ + return (utf8_str_to_upper_or_lower(s, U8_TEXTPREP_TOUPPER)); +} + +char * +utf8_str_tolower(const char *s) +{ + return (utf8_str_to_upper_or_lower(s, U8_TEXTPREP_TOLOWER)); +} |