/* * 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_smb.c,v 1.35 2005/01/28 23:54:13 lindak Exp $ */ /* * various SMB requests. Most of the routines merely packs data into mbufs. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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_COREPLUS, "MICROSOFT NETWORKS 1.03"}, {SMB_DIALECT_LANMAN1_0, "MICROSOFT NETWORKS 3.0"}, {SMB_DIALECT_LANMAN1_0, "LANMAN1.0"}, {SMB_DIALECT_LANMAN2_0, "LM1.2X002"}, {SMB_DIALECT_LANMAN2_1, "LANMAN2.1"}, {SMB_DIALECT_NTLM0_12, "NT LANMAN 1.0"}, {SMB_DIALECT_NTLM0_12, "NT LM 0.12"}, {-1, NULL} }; #define SMB_DIALECT_MAX (sizeof(smb_dialects) / sizeof(struct smb_dialect) - 2) u_int32_t smb_vc_maxread(struct smb_vc *vcp) { /* * Specs say up to 64k data bytes, but Windows traffic * uses 60k... no doubt for some good reason. */ if (vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_READX) return (60*1024); else return (vcp->vc_sopt.sv_maxtx); } u_int32_t smb_vc_maxwrite(struct smb_vc *vcp) { /* * Specs say up to 64k data bytes, but Windows traffic * uses 60k... probably for some good reason. */ if (vcp->vc_sopt.sv_caps & SMB_CAP_LARGE_WRITEX) return (60*1024); else return (vcp->vc_sopt.sv_maxtx); } static int smb_smb_nomux(struct smb_vc *vcp, struct smb_cred *scred, const char *name) { if (scred == &vcp->vc_iod->iod_scred) return 0; SMBERROR("wrong function called(%s)\n", name); return EINVAL; } int smb_smb_negotiate(struct smb_vc *vcp, struct smb_cred *scred) { struct smb_dialect *dp; struct smb_sopt *sp = NULL; struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; u_int8_t wc, stime[8], sblen; u_int16_t dindex, tw, tw1, swlen, bc; int error, maxqsz; int unicode = 0; char *servercs; void *servercshandle = NULL; void *localcshandle = NULL; u_int16_t toklen; if (smb_smb_nomux(vcp, scred, __FUNCTION__) != 0) return EINVAL; vcp->vc_hflags = SMB_FLAGS_CASELESS; /* Disable Unicode for SMB_COM_NEGOTIATE requests */ vcp->vc_hflags &= ~SMB_FLAGS2_UNICODE; vcp->vc_hflags2 |= SMB_FLAGS2_ERR_STATUS; vcp->obj.co_flags &= ~(SMBV_ENCRYPT); sp = &vcp->vc_sopt; bzero(sp, sizeof(struct smb_sopt)); error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_NEGOTIATE, scred, &rqp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); for(dp = smb_dialects; dp->d_id != -1; dp++) { mb_put_uint8(mbp, SMB_DT_DIALECT); smb_put_dstring(mbp, vcp, dp->d_name, SMB_CS_NONE); } smb_rq_bend(rqp); error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); if (error) goto bad; smb_rq_getreply(rqp, &mdp); do { error = md_get_uint8(mdp, &wc); if (error) break; error = md_get_uint16le(mdp, &dindex); if (error) break; error = EBADRPC; if (dindex > 7) { SMBERROR("Don't know how to talk with server %s (%d)\n", "xxx", dindex); break; } dp = smb_dialects + dindex; sp->sv_proto = dp->d_id; SMBSDEBUG("Dialect %s (%d, %d)\n", dp->d_name, dindex, wc); if (dp->d_id >= SMB_DIALECT_NTLM0_12) { if (wc != 17) break; md_get_uint8(mdp, &sp->sv_sm); md_get_uint16le(mdp, &sp->sv_maxmux); md_get_uint16le(mdp, &sp->sv_maxvcs); md_get_uint32le(mdp, &sp->sv_maxtx); md_get_uint32le(mdp, &sp->sv_maxraw); md_get_uint32le(mdp, &sp->sv_skey); md_get_uint32le(mdp, &sp->sv_caps); md_get_mem(mdp, stime, 8, MB_MSYSTEM); md_get_uint16le(mdp, (u_int16_t*)&sp->sv_tz); md_get_uint8(mdp, &sblen); error = md_get_uint16le(mdp, &bc); if (error) break; if (sp->sv_sm & SMB_SM_SIGS_REQUIRE) SMBERROR("server configuration requires packet signing, which we dont support\n"); if (sp->sv_caps & SMB_CAP_UNICODE) { /* * They do Unicode. */ vcp->obj.co_flags |= SMBV_UNICODE; unicode = 1; } if (!(sp->sv_caps & SMB_CAP_STATUS32)) { /* * 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. */ vcp->vc_hflags2 &= ~SMB_FLAGS2_ERR_STATUS; } if (dp->d_id == SMB_DIALECT_NTLM0_12 && sp->sv_maxtx < 4096 && (sp->sv_caps & SMB_CAP_NT_SMBS) == 0) { vcp->obj.co_flags |= SMBV_WIN95; SMBSDEBUG("Win95 detected\n"); } /* * 3 cases here: * * 1) Extended security. * Read bc bytes below for security blob. * Note that we DON'T put the Caps flag in outtok. * outtoklen = bc * * 2) No extended security, have challenge data and * possibly a domain name (which might be zero * bytes long, meaning "missing"). * Copy challenge stuff to vcp->vc_ch (sblen bytes), * then copy Cap flags and domain name (bc-sblen * bytes) to outtok. * outtoklen = bc-sblen+4, where the 4 is for the * Caps flag. * * 3) No extended security, no challenge data, just * possibly a domain name. * Copy Capsflags and domain name (bc) to outtok. * outtoklen = bc+4, where 4 is for the Caps flag */ /* * Sanity check: make sure the challenge length * isn't bigger than the byte count. */ if (sblen > bc) { error = EBADRPC; break; } toklen = bc; if (sblen && sblen <= SMB_MAXCHALLENGELEN && sp->sv_sm & SMB_SM_ENCRYPT) { error = md_get_mem(mdp, vcp->vc_ch, sblen, MB_MSYSTEM); if (error) break; vcp->vc_chlen = sblen; vcp->obj.co_flags |= SMBV_ENCRYPT; toklen -= sblen; } /* For servers that don't support unicode * there are 2 things we could do: * 1) Pass the server Caps flags up to the * user level so the logic up there will * know whether the domain name is unicode * (this is what I did). * 2) Try to convert the non-unicode string * to unicode. This doubles the length of * the outtok buffer and would be guessing that * the string was single-byte ascii, and that * might be wrong. Why ask for trouble? */ /* Warning: NetApp may omit the GUID */ if (!(sp->sv_caps & SMB_CAP_EXT_SECURITY)) { /* * No extended security. * Stick domain name, if present, * and caps in outtok. */ toklen = toklen + 4; /* space for Caps flags */ vcp->vc_outtoklen = toklen; vcp->vc_outtok = malloc(toklen, M_SMBTEMP, M_WAITOK); /* first store server capability bits */ *(u_int32_t *)(vcp->vc_outtok) = sp->sv_caps; /* * Then store the domain name if present; * be sure to subtract 4 from the length * for the Caps flag. */ if (toklen > 4) { error = md_get_mem(mdp, vcp->vc_outtok+4, toklen-4, MB_MSYSTEM); } } else { /* * Extended security. * Stick the rest of the buffer in outtok. */ vcp->vc_outtoklen = toklen; vcp->vc_outtok = malloc(toklen, M_SMBTEMP, M_WAITOK); error = md_get_mem(mdp, vcp->vc_outtok, toklen, MB_MSYSTEM); } break; } vcp->vc_hflags2 &= ~(SMB_FLAGS2_EXT_SEC|SMB_FLAGS2_DFS| SMB_FLAGS2_ERR_STATUS|SMB_FLAGS2_UNICODE); if (dp->d_id > SMB_DIALECT_CORE) { md_get_uint16le(mdp, &tw); sp->sv_sm = tw; md_get_uint16le(mdp, &tw); sp->sv_maxtx = tw; md_get_uint16le(mdp, &sp->sv_maxmux); md_get_uint16le(mdp, &sp->sv_maxvcs); md_get_uint16le(mdp, &tw); /* rawmode */ md_get_uint32le(mdp, &sp->sv_skey); if (wc == 13) { /* >= LANMAN1 */ md_get_uint16(mdp, &tw); /* time */ md_get_uint16(mdp, &tw1); /* date */ md_get_uint16le(mdp, (u_int16_t*)&sp->sv_tz); md_get_uint16le(mdp, &swlen); if (swlen > SMB_MAXCHALLENGELEN) break; md_get_uint16(mdp, NULL); /* mbz */ if (md_get_uint16le(mdp, &bc) != 0) break; if (bc < swlen) break; if (swlen && (sp->sv_sm & SMB_SM_ENCRYPT)) { error = md_get_mem(mdp, vcp->vc_ch, swlen, MB_MSYSTEM); if (error) break; vcp->vc_chlen = swlen; vcp->obj.co_flags |= SMBV_ENCRYPT; } } } else { /* an old CORE protocol */ vcp->vc_hflags2 &= ~SMB_FLAGS2_KNOWS_LONG_NAMES; sp->sv_maxmux = 1; } error = 0; } while (0); if (error == 0) { vcp->vc_maxvcs = sp->sv_maxvcs; if (vcp->vc_maxvcs <= 1) { if (vcp->vc_maxvcs == 0) vcp->vc_maxvcs = 1; } if (sp->sv_maxtx <= 0 || sp->sv_maxtx > 0xffff) sp->sv_maxtx = 1024; else sp->sv_maxtx = min(sp->sv_maxtx, 63*1024 + SMB_HDRLEN + 16); SMB_TRAN_GETPARAM(vcp, SMBTP_RCVSZ, &maxqsz); vcp->vc_rxmax = min(smb_vc_maxread(vcp), maxqsz - 1024); SMB_TRAN_GETPARAM(vcp, SMBTP_SNDSZ, &maxqsz); vcp->vc_wxmax = min(smb_vc_maxwrite(vcp), maxqsz - 1024); vcp->vc_txmax = min(sp->sv_maxtx, maxqsz); SMBSDEBUG("TZ = %d\n", sp->sv_tz); SMBSDEBUG("CAPS = %x\n", sp->sv_caps); SMBSDEBUG("MAXMUX = %d\n", sp->sv_maxmux); SMBSDEBUG("MAXVCS = %d\n", sp->sv_maxvcs); SMBSDEBUG("MAXRAW = %d\n", sp->sv_maxraw); SMBSDEBUG("MAXTX = %d\n", sp->sv_maxtx); } /* * If the server supports Unicode, set up to use Unicode * when talking to them. Othewise, use code page 437. */ if (unicode) servercs = "ucs-2"; else { /* * todo: if we can't determine the server's encoding, we * need to try a best-guess here. */ servercs = "cp437"; } error = iconv_open(servercs, "utf-8", &servercshandle); if (error != 0) goto bad; error = iconv_open("utf-8", servercs, &localcshandle); if (error != 0) { iconv_close(servercshandle); goto bad; } if (vcp->vc_toserver) iconv_close(vcp->vc_toserver); if (vcp->vc_tolocal) iconv_close(vcp->vc_tolocal); vcp->vc_toserver = servercshandle; vcp->vc_tolocal = localcshandle; if (unicode) vcp->vc_hflags2 |= SMB_FLAGS2_UNICODE; bad: smb_rq_done(rqp); return error; } static void get_ascii_password(struct smb_vc *vcp, int upper, char *pbuf) { if (upper) { iconv_convstr(vcp->vc_toupper, pbuf, smb_vc_getpass(vcp), SMB_MAXPASSWORDLEN); } else { strncpy(pbuf, smb_vc_getpass(vcp), SMB_MAXPASSWORDLEN); pbuf[SMB_MAXPASSWORDLEN] = '\0'; } } static void get_unicode_password(struct smb_vc *vcp, char *pbuf) { strncpy(pbuf, smb_vc_getpass(vcp), SMB_MAXPASSWORDLEN); pbuf[SMB_MAXPASSWORDLEN] = '\0'; } static u_char * add_name_to_blob(u_char *blobnames, struct smb_vc *vcp, const u_char *name, size_t namelen, int nametype, int uppercase) { struct ntlmv2_namehdr namehdr; char *namebuf; u_int16_t *uninamebuf; size_t uninamelen; if (name != NULL) { uninamebuf = malloc(2 * namelen, M_SMBTEMP, M_WAITOK); if (uppercase) { namebuf = malloc(namelen + 1, M_SMBTEMP, M_WAITOK); iconv_convstr(vcp->vc_toupper, namebuf, name, namelen); uninamelen = smb_strtouni(uninamebuf, namebuf, namelen, UTF_PRECOMPOSED|UTF_NO_NULL_TERM); free(namebuf, M_SMBTEMP); } else { uninamelen = smb_strtouni(uninamebuf, name, namelen, UTF_PRECOMPOSED|UTF_NO_NULL_TERM); } } else { uninamelen = 0; uninamebuf = NULL; } namehdr.type = htoles(nametype); namehdr.len = htoles(uninamelen); bcopy(&namehdr, blobnames, sizeof namehdr); blobnames += sizeof namehdr; if (uninamebuf != NULL) { bcopy(uninamebuf, blobnames, uninamelen); blobnames += uninamelen; free(uninamebuf, M_SMBTEMP); } return blobnames; } static u_char * make_ntlmv2_blob(struct smb_vc *vcp, u_int64_t client_nonce, size_t *bloblen) { u_char *blob; size_t blobsize; size_t domainlen, srvlen; struct ntlmv2_blobhdr *blobhdr; struct timespec now; u_int64_t timestamp; u_char *blobnames; /* * XXX - the information at * * http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response * * says that the "target information" comes from the Type 2 message, * but, as we're not doing NTLMSSP, we don't have that. * * Should we use the names from the NegProt response? Can we trust * the NegProt response? (I've seen captures where the primary * domain name has an extra byte in front of it.) * * For now, we don't trust it - we use vcp->vc_domain and * vcp->vc_srvname, instead. We upper-case them and convert * them to Unicode, as that's what's supposed to be in the blob. */ domainlen = strlen(vcp->vc_domain); srvlen = strlen(vcp->vc_srvname); blobsize = sizeof(struct ntlmv2_blobhdr) + 3*sizeof (struct ntlmv2_namehdr) + 4 + 2*domainlen + 2*srvlen; blob = malloc(blobsize, M_SMBTEMP, M_WAITOK); bzero(blob, blobsize); blobhdr = (struct ntlmv2_blobhdr *)blob; blobhdr->header = htolel(0x00000101); nanotime(&now); smb_time_local2NT(&now, 0, ×tamp); blobhdr->timestamp = htoleq(timestamp); blobhdr->client_nonce = client_nonce; blobnames = blob + sizeof (struct ntlmv2_blobhdr); blobnames = add_name_to_blob(blobnames, vcp, vcp->vc_domain, domainlen, NAMETYPE_DOMAIN_NB, 1); blobnames = add_name_to_blob(blobnames, vcp, vcp->vc_srvname, srvlen, NAMETYPE_MACHINE_NB, 1); blobnames = add_name_to_blob(blobnames, vcp, NULL, 0, NAMETYPE_EOL, 0); *bloblen = blobnames - blob; return (blob); } static char * uppercasify_string(struct smb_vc *vcp, const char *string) { size_t stringlen; char *ucstrbuf; stringlen = strlen(string); ucstrbuf = malloc(stringlen + 1, M_SMBTEMP, M_WAITOK); iconv_convstr(vcp->vc_toupper, ucstrbuf, string, stringlen); return (ucstrbuf); } /* * When not doing Kerberos, we can try, in order: * * NTLMv2 * NTLM with the ASCII password not upper-cased * NTLM with the ASCII password upper-cased * * if the server supports encrypted passwords, or * * plain-text with the ASCII password not upper-cased * plain-text with the ASCII password upper-cased * * if it doesn't. */ #define STATE_NTLMV2 0 #define STATE_NOUCPW 1 #define STATE_UCPW 2 int smb_smb_ssnsetup(struct smb_vc *vcp, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; u_int8_t wc; /* u_int16_t tw, tw1;*/ int minauth; smb_uniptr unipp = NULL, ntencpass = NULL; char *pp = NULL, *up, *ucup, *ucdp; char *pbuf = NULL; char *encpass = NULL; int error, ulen; size_t plen = 0, uniplen = 0; int state; size_t ntlmv2_bloblen; u_char *ntlmv2_blob; u_int64_t client_nonce; u_int32_t caps = 0; u_int16_t bl; /* BLOB length */ u_int16_t stringlen; caps |= SMB_CAP_LARGE_FILES; if (vcp->obj.co_flags & SMBV_UNICODE) caps |= SMB_CAP_UNICODE; if (vcp->vc_sopt.sv_caps & SMB_CAP_NT_SMBS) caps |= SMB_CAP_NT_SMBS; /* we do if they do */ minauth = vcp->obj.co_flags & SMBV_MINAUTH; if (vcp->vc_intok) { if (vcp->vc_intoklen > 65536 || !(vcp->vc_hflags2 & SMB_FLAGS2_EXT_SEC) || SMB_DIALECT(vcp) < SMB_DIALECT_NTLM0_12) return EINVAL; vcp->vc_smbuid = 0; } if (vcp->vc_hflags2 & SMB_FLAGS2_ERR_STATUS) caps |= SMB_CAP_STATUS32; if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) state = STATE_NTLMV2; /* try NTLMv2 first */ else state = STATE_NOUCPW; /* try plain-text mixed-case first */ again: if (!vcp->vc_intok) vcp->vc_smbuid = SMB_UID_UNKNOWN; if (smb_smb_nomux(vcp, scred, __FUNCTION__) != 0) return EINVAL; if (!vcp->vc_intok) { /* * We're not doing extended security, which, for * now, means we're not doing Kerberos. * Fail if the minimum authentication level is * Kerberos. */ if (minauth >= SMBV_MINAUTH_KERBEROS) return EAUTH; if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) { /* * Encrypted passwords. * If we're going to try NTLM, fail if the minimum * authentication level is NTLMv2 or better. */ if (state > STATE_NTLMV2) { if (minauth >= SMBV_MINAUTH_NTLMV2) return EAUTH; } } else { /* * Plain-text passwords. * Fail if the minimum authentication level is * LM or better. */ if (minauth >= SMBV_MINAUTH_LM) return EAUTH; } } error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_SESSION_SETUP_ANDX, scred, &rqp); if (error) return error; /* * Domain name must be upper-case, as that's what's used * when computing LMv2 and NTLMv2 responses - and, for NTLMv2, * the domain name in the request has to be upper-cased as well. * (That appears not to be the case for the user name. Go * figure.) * * don't need to uppercase domain string. It's already uppercase UTF-8. */ stringlen = strlen(vcp->vc_domain)+1 /* strlen doesn't count null */; ucdp = malloc(stringlen, M_SMBTEMP, M_WAITOK); memcpy(ucdp,vcp->vc_domain,stringlen); if (vcp->vc_intok) { caps |= SMB_CAP_EXT_SECURITY; } else if (!(vcp->vc_sopt.sv_sm & SMB_SM_USER)) { /* * In the share security mode password will be used * only in the tree authentication */ pp = ""; plen = 1; unipp = &smb_unieol; uniplen = sizeof(smb_unieol); } else { pbuf = malloc(SMB_MAXPASSWORDLEN + 1, M_SMBTEMP, M_WAITOK); if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) { if (state == STATE_NTLMV2) { /* * Compute the LMv2 and NTLMv2 responses, * derived from the challenge, the user name, * the domain/workgroup into which we're * logging, and the Unicode password. */ get_unicode_password(vcp, pbuf); /* * Construct the client nonce by getting * a bunch of random data. */ read_random((void *) &client_nonce, sizeof(client_nonce)); /* * Convert the user name to upper-case, as * that's what's used when computing LMv2 * and NTLMv2 responses. */ ucup = uppercasify_string(vcp, vcp->vc_username); /* * Compute the LMv2 response, derived * from the server challenge, the * user name, the domain/workgroup * into which we're logging, the * client nonce, and the Unicode * password. */ smb_ntlmv2response(pbuf, ucup, ucdp, vcp->vc_ch, (u_char *)&client_nonce, 8, (u_char**)&encpass, &plen); pp = encpass; /* * Construct the blob. */ ntlmv2_blob = make_ntlmv2_blob(vcp, client_nonce, &ntlmv2_bloblen); /* * Compute the NTLMv2 response, derived * from the server challenge, the * user name, the domain/workgroup * into which we're logging, the * blob, and the Unicode password. */ smb_ntlmv2response(pbuf, ucup, ucdp, vcp->vc_ch, ntlmv2_blob, ntlmv2_bloblen, (u_char**)&ntencpass, &uniplen); free(ucup, M_SMBTEMP); free(ntlmv2_blob, M_SMBTEMP); unipp = ntencpass; } else { plen = 24; encpass = malloc(plen, M_SMBTEMP, M_WAITOK); if (minauth >= SMBV_MINAUTH_NTLM) { /* * Don't put the LM response on * the wire - it's too easy to * crack. */ bzero(encpass, plen); } else { /* * Compute the LM response, derived * from the challenge and the ASCII * password. * * We try w/o uppercasing first so * Samba mixed case passwords work. * If that fails, we come back and * try uppercasing to satisfy OS/2 * and Windows for Workgroups. */ get_ascii_password(vcp, (state == STATE_UCPW), pbuf); smb_lmresponse(pbuf, vcp->vc_ch, encpass); } pp = encpass; /* * Compute the NTLM response, derived from * the challenge and the Unicode password. */ get_unicode_password(vcp, pbuf); uniplen = 24; ntencpass = malloc(uniplen, M_SMBTEMP, M_WAITOK); smb_ntlmresponse(pbuf, vcp->vc_ch, (u_char*)ntencpass); unipp = ntencpass; } } else { /* * We try w/o uppercasing first so Samba mixed case * passwords work. If that fails, we come back and * try uppercasing to satisfy OS/2 and Windows for * Workgroups. */ get_ascii_password(vcp, (state == STATE_UCPW), pbuf); plen = strlen(pbuf) + 1; pp = pbuf; uniplen = plen * 2; ntencpass = malloc(uniplen, M_SMBTEMP, M_WAITOK); (void)smb_strtouni(ntencpass, smb_vc_getpass(vcp), 0, UTF_PRECOMPOSED); plen--; /* * The uniplen is zeroed because Samba cannot deal * with this 2nd cleartext password. This Samba * "bug" is actually a workaround for problems in * Microsoft clients. */ uniplen = 0/*-= 2*/; unipp = ntencpass; } } smb_rq_wstart(rqp); mbp = &rqp->sr_rq; up = vcp->vc_username; /* * If userid is null we are attempting anonymous browse login * so passwords must be zero length. */ if (*up == '\0') plen = uniplen = 0; ulen = strlen(up) + 1; mb_put_uint8(mbp, 0xff); mb_put_uint8(mbp, 0); mb_put_uint16le(mbp, 0); mb_put_uint16le(mbp, vcp->vc_sopt.sv_maxtx); mb_put_uint16le(mbp, vcp->vc_sopt.sv_maxmux); mb_put_uint16le(mbp, vcp->vc_number); mb_put_uint32le(mbp, vcp->vc_sopt.sv_skey); if (SMB_DIALECT(vcp) < SMB_DIALECT_NTLM0_12) { mb_put_uint16le(mbp, plen); mb_put_uint32le(mbp, 0); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_mem(mbp, pp, plen, MB_MSYSTEM); smb_put_dstring(mbp, vcp, up, SMB_CS_NONE); } else { if (vcp->vc_intok) { mb_put_uint16le(mbp, vcp->vc_intoklen); mb_put_uint32le(mbp, 0); /* reserved */ mb_put_uint32le(mbp, caps); /* my caps */ smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_mem(mbp, vcp->vc_intok, vcp->vc_intoklen, MB_MSYSTEM); /* security blob */ } else { mb_put_uint16le(mbp, plen); mb_put_uint16le(mbp, uniplen); mb_put_uint32le(mbp, 0); /* reserved */ mb_put_uint32le(mbp, caps); /* my caps */ smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_mem(mbp, pp, plen, MB_MSYSTEM); /* password */ mb_put_mem(mbp, (caddr_t)unipp, uniplen, MB_MSYSTEM); smb_put_dstring(mbp, vcp, up, SMB_CS_NONE); /* user */ smb_put_dstring(mbp, vcp, ucdp, SMB_CS_NONE); /* domain */ } smb_put_dstring(mbp, vcp, "MacOSX", SMB_CS_NONE); smb_put_dstring(mbp, vcp, "NETSMB", SMB_CS_NONE); /* LAN Mgr */ } smb_rq_bend(rqp); if (ntencpass) { free(ntencpass, M_SMBTEMP); ntencpass = NULL; } free(ucdp, M_SMBTEMP); error = smb_rq_simple_timed(rqp, SMBSSNSETUPTIMO); SMBSDEBUG("%d\n", error); if (error) { if (rqp->sr_errclass == ERRDOS && rqp->sr_serror == ERRnoaccess) error = EAUTH; if (!(rqp->sr_errclass == ERRDOS && rqp->sr_serror == ERRmoredata)) goto bad; } vcp->vc_smbuid = rqp->sr_rpuid; smb_rq_getreply(rqp, &mdp); do { error = md_get_uint8(mdp, &wc); if (error) break; error = EBADRPC; if (vcp->vc_intok) { if (wc != 4) break; } else if (wc != 3) break; md_get_uint8(mdp, NULL); /* secondary cmd */ md_get_uint8(mdp, NULL); /* mbz */ md_get_uint16le(mdp, NULL); /* andxoffset */ md_get_uint16le(mdp, NULL); /* action */ if (vcp->vc_intok) md_get_uint16le(mdp, &bl); /* ext security */ md_get_uint16le(mdp, NULL); /* byte count */ if (vcp->vc_intok) { vcp->vc_outtoklen = bl; vcp->vc_outtok = malloc(bl, M_SMBTEMP, M_WAITOK); error = md_get_mem(mdp, vcp->vc_outtok, bl, MB_MSYSTEM); if (error) break; } /* server OS, LANMGR, & Domain here*/ error = 0; } while (0); bad: if (encpass) { free(encpass, M_SMBTEMP); encpass = NULL; } if (pbuf) { free(pbuf, M_SMBTEMP); pbuf = NULL; } smb_rq_done(rqp); if (error && (vcp->vc_sopt.sv_sm & SMB_SM_USER) && !(vcp->vc_intok)) { /* * We're doing user-level authentication (so we are actually * sending authentication stuff over the wire), and we're * not doing extended security), and the stuff we tried * failed. Should we try the next type of authentication? */ if (state < STATE_UCPW) { /* * Yes, we still have more to try. */ state++; goto again; } } return error; } int smb_smb_ssnclose(struct smb_vc *vcp, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; int error; if (vcp->vc_smbuid == SMB_UID_UNKNOWN) return 0; if (smb_smb_nomux(vcp, scred, __FUNCTION__) != 0) return EINVAL; error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_LOGOFF_ANDX, scred, &rqp); if (error) return error; mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); mb_put_uint8(mbp, 0); mb_put_uint16le(mbp, 0); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); return error; } static char smb_any_share[] = "?????"; static char * smb_share_typename(int stype) { char *pp; switch (stype) { case SMB_ST_DISK: pp = "A:"; break; case SMB_ST_PRINTER: pp = smb_any_share; /* can't use LPT: here... */ break; case SMB_ST_PIPE: pp = "IPC"; break; case SMB_ST_COMM: pp = "COMM"; break; case SMB_ST_ANY: default: pp = smb_any_share; break; } return pp; } int smb_smb_treeconnect(struct smb_share *ssp, struct smb_cred *scred) { struct smb_vc *vcp; struct smb_rq rq, *rqp = &rq; struct mbchain *mbp; char *pp, *pbuf, *encpass; int error, plen, caseopt; int upper = 0; again: vcp = SSTOVC(ssp); ssp->ss_tid = SMB_TID_UNKNOWN; error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_TREE_CONNECT_ANDX, scred, &rqp); if (error) return error; caseopt = SMB_CS_NONE; if (vcp->vc_sopt.sv_sm & SMB_SM_USER) { plen = 1; pp = ""; pbuf = NULL; encpass = NULL; } else { pbuf = malloc(SMB_MAXPASSWORDLEN + 1, M_SMBTEMP, M_WAITOK); encpass = malloc(24, M_SMBTEMP, M_WAITOK); /* * We try w/o uppercasing first so Samba mixed case * passwords work. If that fails we come back and try * uppercasing to satisfy OS/2 and Windows for Workgroups. */ if (upper++) { iconv_convstr(vcp->vc_toupper, pbuf, smb_share_getpass(ssp), SMB_MAXPASSWORDLEN); } else { strncpy(pbuf, smb_share_getpass(ssp), SMB_MAXPASSWORDLEN); pbuf[SMB_MAXPASSWORDLEN] = '\0'; } iconv_convstr(vcp->vc_toserver, pbuf, pbuf, SMB_MAXPASSWORDLEN); if (vcp->vc_sopt.sv_sm & SMB_SM_ENCRYPT) { plen = 24; smb_lmresponse(pbuf, vcp->vc_ch, encpass); pp = encpass; } else { plen = strlen(pbuf) + 1; pp = pbuf; } } mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); mb_put_uint8(mbp, 0); mb_put_uint16le(mbp, 0); mb_put_uint16le(mbp, 0); /* Flags */ mb_put_uint16le(mbp, plen); smb_rq_wend(rqp); smb_rq_bstart(rqp); error = mb_put_mem(mbp, pp, plen, MB_MSYSTEM); if (error) { SMBERROR("error %d from mb_put_mem for pp\n", error); goto bad; } smb_put_dmem(mbp, vcp, "\\\\", 2, caseopt, NULL); pp = vcp->vc_srvname; error = smb_put_dmem(mbp, vcp, pp, strlen(pp), caseopt, NULL); if (error) { SMBERROR("error %d from smb_put_dmem for srvname\n", error); goto bad; } smb_put_dmem(mbp, vcp, "\\", 1, caseopt, NULL); pp = ssp->ss_name; error = smb_put_dstring(mbp, vcp, pp, caseopt); if (error) { SMBERROR("error %d from smb_put_dstring for ss_name\n", error); goto bad; } /* The type name is always ASCII */ pp = smb_share_typename(ssp->ss_type); error = mb_put_mem(mbp, pp, strlen(pp) + 1, MB_MSYSTEM); if (error) { SMBERROR("error %d from mb_put_mem for ss_type\n", error); goto bad; } smb_rq_bend(rqp); error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); if (error) goto bad; ssp->ss_tid = rqp->sr_rptid; ssp->ss_vcgenid = vcp->vc_genid; ssp->ss_flags |= SMBS_CONNECTED; bad: if (encpass) free(encpass, M_SMBTEMP); if (pbuf) free(pbuf, M_SMBTEMP); smb_rq_done(rqp); if (error && upper == 1) goto again; return error; } int smb_smb_treedisconnect(struct smb_share *ssp, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; int error; if (ssp->ss_tid == SMB_TID_UNKNOWN) return 0; error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_TREE_DISCONNECT, scred, &rqp); if (error) return error; mbp = &rqp->sr_rq; smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); ssp->ss_tid = SMB_TID_UNKNOWN; return error; } static __inline int smb_smb_readx(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid, uio_t uio, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; u_int8_t wc; int error; u_int16_t residhi, residlo, off, doff; u_int32_t resid; error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_READ_ANDX, scred, &rqp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); /* no secondary command */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, 0); /* offset to secondary */ mb_put_mem(mbp, (caddr_t)&fid, sizeof(fid), MB_MSYSTEM); mb_put_uint32le(mbp, (u_int32_t)uio_offset(uio)); *len = min(SSTOVC(ssp)->vc_rxmax, *len); mb_put_uint16le(mbp, (u_int16_t)*len); /* MaxCount */ mb_put_uint16le(mbp, (u_int16_t)*len); /* MinCount (only indicates blocking) */ mb_put_uint32le(mbp, (unsigned)*len >> 16); /* MaxCountHigh */ mb_put_uint16le(mbp, (u_int16_t)*len); /* Remaining ("obsolete") */ mb_put_uint32le(mbp, (u_int32_t)(uio_offset(uio) >> 32)); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); do { error = smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); off = SMB_HDRLEN; md_get_uint8(mdp, &wc); off++; if (wc != 12) { error = EBADRPC; break; } md_get_uint8(mdp, NULL); off++; md_get_uint8(mdp, NULL); off++; md_get_uint16le(mdp, NULL); off += 2; md_get_uint16le(mdp, NULL); off += 2; md_get_uint16le(mdp, NULL); /* data compaction mode */ off += 2; md_get_uint16le(mdp, NULL); off += 2; md_get_uint16le(mdp, &residlo); off += 2; md_get_uint16le(mdp, &doff); /* data offset */ off += 2; md_get_uint16le(mdp, &residhi); off += 2; resid = (residhi << 16) | residlo; md_get_mem(mdp, NULL, 4 * 2, MB_MSYSTEM); off += 4*2; md_get_uint16le(mdp, NULL); /* ByteCount */ off += 2; if (doff > off) /* pad byte(s)? */ md_get_mem(mdp, NULL, doff - off, MB_MSYSTEM); if (resid == 0) { *rresid = resid; break; } error = md_get_uio(mdp, uio, resid); if (error) break; *rresid = resid; } while(0); smb_rq_done(rqp); return (error); } static __inline int smb_smb_writex(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid, uio_t uio, struct smb_cred *scred, int timo) { struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; int error; u_int8_t wc; u_int16_t resid; if (SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_WRITEX) { *len = min(*len, 0x1ffff); } else { *len = min(*len, min(0xffff, SSTOVC(ssp)->vc_txmax - SMB_HDRLEN - 34)); /* (data starts 18 bytes further in than SMB_COM_WRITE) */ } error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_WRITE_ANDX, scred, &rqp); if (error) return (error); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_uint8(mbp, 0xff); /* no secondary command */ mb_put_uint8(mbp, 0); /* MBZ */ mb_put_uint16le(mbp, 0); /* offset to secondary */ mb_put_mem(mbp, (caddr_t)&fid, sizeof(fid), MB_MSYSTEM); mb_put_uint32le(mbp, (u_int32_t)uio_offset(uio)); mb_put_uint32le(mbp, 0); /* MBZ (timeout) */ mb_put_uint16le(mbp, 0); /* !write-thru */ mb_put_uint16le(mbp, 0); *len = min(SSTOVC(ssp)->vc_wxmax, *len); mb_put_uint16le(mbp, (u_int16_t)((unsigned)*len >> 16)); mb_put_uint16le(mbp, (u_int16_t)*len); mb_put_uint16le(mbp, 64); /* data offset from header start */ mb_put_uint32le(mbp, (u_int32_t)(uio_offset(uio) >> 32)); smb_rq_wend(rqp); smb_rq_bstart(rqp); do { mb_put_uint8(mbp, 0xee); /* mimic xp pad byte! */ error = mb_put_uio(mbp, uio, *len); if (error) break; smb_rq_bend(rqp); error = timo ? smb_rq_simple_timed(rqp, timo) : smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); md_get_uint8(mdp, &wc); if (wc != 6) { error = EBADRPC; break; } md_get_uint8(mdp, NULL); md_get_uint8(mdp, NULL); md_get_uint16le(mdp, NULL); md_get_uint16le(mdp, &resid); /* actually is # written */ *rresid = resid; /* * if LARGE_WRITEX then there's one more bit of # written */ if ((SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_WRITEX)) { md_get_uint16le(mdp, NULL); md_get_uint16le(mdp, &resid); *rresid |= (int)(resid & 1) << 16; } } while(0); smb_rq_done(rqp); return (error); } static __inline int smb_smb_read(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid, uio_t uio, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; u_int16_t resid, bc; u_int8_t wc; int error, rlen; if (SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_FILES || SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_READX) return (smb_smb_readx(ssp, fid, len, rresid, uio, scred)); error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_READ, scred, &rqp); if (error) return error; *len = rlen = min(*len, min(0xffff, SSTOVC(ssp)->vc_txmax - SMB_HDRLEN - 16)); smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_mem(mbp, (caddr_t)&fid, sizeof(fid), MB_MSYSTEM); mb_put_uint16le(mbp, (u_int16_t)rlen); mb_put_uint32le(mbp, (u_int32_t)uio_offset(uio)); mb_put_uint16le(mbp, (u_int16_t)min(uio_resid(uio), 0xffff)); smb_rq_wend(rqp); smb_rq_bstart(rqp); smb_rq_bend(rqp); do { error = smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); md_get_uint8(mdp, &wc); if (wc != 5) { error = EBADRPC; break; } md_get_uint16le(mdp, &resid); md_get_mem(mdp, NULL, 4 * 2, MB_MSYSTEM); md_get_uint16le(mdp, &bc); md_get_uint8(mdp, NULL); /* ignore buffer type */ md_get_uint16le(mdp, &resid); if (resid == 0) { *rresid = resid; break; } error = md_get_uio(mdp, uio, resid); if (error) break; *rresid = resid; } while(0); smb_rq_done(rqp); return error; } int smb_read(struct smb_share *ssp, u_int16_t fid, uio_t uio, struct smb_cred *scred) { int tsize, len, resid; int error = 0; tsize = uio_resid(uio); while (tsize > 0) { len = tsize; error = smb_smb_read(ssp, fid, &len, &resid, uio, scred); if (error) break; tsize -= resid; if (resid < len) break; } return error; } static __inline int smb_smb_write(struct smb_share *ssp, u_int16_t fid, int *len, int *rresid, uio_t uio, struct smb_cred *scred, int timo) { struct smb_rq *rqp; struct mbchain *mbp; struct mdchain *mdp; u_int16_t resid; u_int8_t wc; int error; if (uio_offset(uio) + *len > UINT32_MAX && !(SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_FILES)) return (EFBIG); if (*len && (SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_FILES || SSTOVC(ssp)->vc_sopt.sv_caps & SMB_CAP_LARGE_WRITEX)) return (smb_smb_writex(ssp, fid, len, rresid, uio, scred, timo)); error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_WRITE, scred, &rqp); if (error) return error; resid = min(*len, min(0xffff, SSTOVC(ssp)->vc_txmax - SMB_HDRLEN - 16)); *len = resid; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); mb_put_mem(mbp, (caddr_t)&fid, sizeof(fid), MB_MSYSTEM); mb_put_uint16le(mbp, resid); mb_put_uint32le(mbp, (u_int32_t)uio_offset(uio)); mb_put_uint16le(mbp, (u_int16_t)min(uio_resid(uio), 0xffff)); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_DATA); mb_put_uint16le(mbp, resid); do { error = mb_put_uio(mbp, uio, resid); if (error) break; smb_rq_bend(rqp); error = timo ? smb_rq_simple_timed(rqp, timo) : smb_rq_simple(rqp); if (error) break; smb_rq_getreply(rqp, &mdp); md_get_uint8(mdp, &wc); if (wc != 1) { error = EBADRPC; break; } md_get_uint16le(mdp, &resid); *rresid = resid; } while(0); smb_rq_done(rqp); return error; } int smb_write(struct smb_share *ssp, u_int16_t fid, uio_t uio, struct smb_cred *scred, int timo) { int error = 0, len, tsize, resid; user_ssize_t old_resid; off_t old_offset; tsize = old_resid = uio_resid(uio); old_offset = uio_offset(uio); while (tsize > 0) { len = tsize; // LP64todo - resid, tsize should be 64-bit values error = smb_smb_write(ssp, fid, &len, &resid, uio, scred, timo); timo = 0; /* only first write is special */ if (error) break; if (resid < len) { error = EIO; break; } tsize -= resid; } if (error) { /* * Errors can happen on the copyin, the rpc, etc. So they * imply resid is unreliable. The only safe thing is * to pretend zero bytes made it. We needn't restore the * iovs because callers don't depend on them in error * paths - uio_resid and uio_offset are what matter. */ uio_setresid(uio, old_resid); uio_setoffset(uio, old_offset); } return error; } static u_int32_t smbechoes = 0; int smb_smb_echo(struct smb_vc *vcp, struct smb_cred *scred, int timo) { struct smb_rq *rqp; struct mbchain *mbp; int error; error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_ECHO, scred, &rqp); if (error) return error; mbp = &rqp->sr_rq; smb_rq_wstart(rqp); mb_put_uint16le(mbp, 1); /* echo count */ smb_rq_wend(rqp); smb_rq_bstart(rqp); smbechoes++; mb_put_uint32le(mbp, smbechoes); smb_rq_bend(rqp); error = smb_rq_simple_timed(rqp, timo); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); return error; } int smb_smb_checkdir(struct smb_share *ssp, struct smbnode *dnp, char *name, int nmlen, struct smb_cred *scred) { struct smb_rq *rqp; struct mbchain *mbp; int error; error = smb_rq_alloc(SSTOCP(ssp), SMB_COM_CHECK_DIRECTORY, scred, &rqp); if (error) return error; smb_rq_getrequest(rqp, &mbp); smb_rq_wstart(rqp); smb_rq_wend(rqp); smb_rq_bstart(rqp); mb_put_uint8(mbp, SMB_DT_ASCII); smbfs_fullpath(mbp, SSTOVC(ssp), dnp, name, &nmlen, '\\'); smb_rq_bend(rqp); error = smb_rq_simple(rqp); SMBSDEBUG("%d\n", error); smb_rq_done(rqp); return error; }