diff options
Diffstat (limited to 'usr/src/lib/libsmbfs/smb/connect.c')
| -rw-r--r-- | usr/src/lib/libsmbfs/smb/connect.c | 535 |
1 files changed, 535 insertions, 0 deletions
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); +} |
