/* Unix SMB/Netbios implementation. Version 3.0 client connect/disconnect routines Copyright (C) Andrew Tridgell 1994-1998 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #define NO_SYSLOG #include "includes.h" static struct { int prot; char *name; } prots[] = { {PROTOCOL_CORE,"PC NETWORK PROGRAM 1.0"}, {PROTOCOL_COREPLUS,"MICROSOFT NETWORKS 1.03"}, {PROTOCOL_LANMAN1,"MICROSOFT NETWORKS 3.0"}, {PROTOCOL_LANMAN1,"LANMAN1.0"}, {PROTOCOL_LANMAN2,"LM1.2X002"}, {PROTOCOL_LANMAN2,"Samba"}, {PROTOCOL_NT1,"NT LANMAN 1.0"}, {PROTOCOL_NT1,"NT LM 0.12"}, {-1,NULL} }; /**************************************************************************** Send a session setup. The username is in UNIX character format and must be converted to DOS codepage format before sending. If the password is in plaintext, the same should be done. ****************************************************************************/ BOOL cli_session_setup(struct cli_state *cli, char *user, char *pass, int passlen, char *ntpass, int ntpasslen, char *workgroup) { char *p; fstring pword, ntpword; if (cli->protocol < PROTOCOL_LANMAN1) return True; if (passlen > sizeof(pword)-1 || ntpasslen > sizeof(ntpword)-1) { return False; } if (((passlen == 0) || (passlen == 1)) && (pass[0] == '\0')) { /* Null session connect. */ pword[0] = '\0'; ntpword[0] = '\0'; } else { if ((cli->sec_mode & 2) && passlen != 24) { /* * Encrypted mode needed, and non encrypted password supplied. */ passlen = 24; ntpasslen = 24; fstrcpy(pword, pass); unix_to_dos(pword,True); fstrcpy(ntpword, ntpass);; unix_to_dos(ntpword,True); SMBencrypt((uchar *)pword,(uchar *)cli->cryptkey,(uchar *)pword); SMBNTencrypt((uchar *)ntpword,(uchar *)cli->cryptkey,(uchar *)ntpword); } else if ((cli->sec_mode & 2) && passlen == 24) { /* * Encrypted mode needed, and encrypted password supplied. */ memcpy(pword, pass, passlen); if(ntpasslen == 24) { memcpy(ntpword, ntpass, ntpasslen); } else { fstrcpy(ntpword, ""); ntpasslen = 0; } } else { /* * Plaintext mode needed, assume plaintext supplied. */ fstrcpy(pword, pass); unix_to_dos(pword,True); fstrcpy(ntpword, ""); ntpasslen = 0; } } /* if in share level security then don't send a password now */ if (!(cli->sec_mode & 1)) { fstrcpy(pword, ""); passlen=1; fstrcpy(ntpword, ""); ntpasslen=1; } /* send a session setup command */ memset(cli->outbuf,'\0',smb_size); if (cli->protocol < PROTOCOL_NT1) { set_message(cli->outbuf,10,1 + strlen(user) + passlen,True); CVAL(cli->outbuf,smb_com) = SMBsesssetupX; cli_setup_packet(cli); CVAL(cli->outbuf,smb_vwv0) = 0xFF; SSVAL(cli->outbuf,smb_vwv2,cli->max_xmit); SSVAL(cli->outbuf,smb_vwv3,2); SSVAL(cli->outbuf,smb_vwv4,1); SIVAL(cli->outbuf,smb_vwv5,cli->sesskey); SSVAL(cli->outbuf,smb_vwv7,passlen); p = smb_buf(cli->outbuf); memcpy(p,pword,passlen); p += passlen; pstrcpy(p,user); unix_to_dos(p,True); strupper(p); } else { set_message(cli->outbuf,13,0,True); CVAL(cli->outbuf,smb_com) = SMBsesssetupX; cli_setup_packet(cli); CVAL(cli->outbuf,smb_vwv0) = 0xFF; SSVAL(cli->outbuf,smb_vwv2,CLI_BUFFER_SIZE); SSVAL(cli->outbuf,smb_vwv3,2); SSVAL(cli->outbuf,smb_vwv4,cli->pid); SIVAL(cli->outbuf,smb_vwv5,cli->sesskey); SSVAL(cli->outbuf,smb_vwv7,passlen); SSVAL(cli->outbuf,smb_vwv8,ntpasslen); SSVAL(cli->outbuf,smb_vwv11,0); p = smb_buf(cli->outbuf); memcpy(p,pword,passlen); p += SVAL(cli->outbuf,smb_vwv7); memcpy(p,ntpword,ntpasslen); p += SVAL(cli->outbuf,smb_vwv8); pstrcpy(p,user); unix_to_dos(p,True); strupper(p); p = skip_string(p,1); pstrcpy(p,workgroup); strupper(p); p = skip_string(p,1); pstrcpy(p,"Unix");p = skip_string(p,1); pstrcpy(p,"Samba");p = skip_string(p,1); set_message(cli->outbuf,13,PTR_DIFF(p,smb_buf(cli->outbuf)),False); } cli_send_smb(cli); if (!cli_receive_smb(cli)) return False; show_msg(cli->inbuf); if (CVAL(cli->inbuf,smb_rcls) != 0) { return False; } /* use the returned vuid from now on */ cli->vuid = SVAL(cli->inbuf,smb_uid); if (cli->protocol >= PROTOCOL_NT1) { /* * Save off some of the connected server * info. */ char *server_domain,*server_os,*server_type; server_os = smb_buf(cli->inbuf); server_type = skip_string(server_os,1); server_domain = skip_string(server_type,1); fstrcpy(cli->server_os, server_os); dos_to_unix(cli->server_os, True); fstrcpy(cli->server_type, server_type); dos_to_unix(cli->server_type, True); fstrcpy(cli->server_domain, server_domain); dos_to_unix(cli->server_domain, True); } fstrcpy(cli->user_name, user); dos_to_unix(cli->user_name, True); return True; } /**************************************************************************** Send a uloggoff. *****************************************************************************/ BOOL cli_ulogoff(struct cli_state *cli) { memset(cli->outbuf,'\0',smb_size); set_message(cli->outbuf,2,0,True); CVAL(cli->outbuf,smb_com) = SMBulogoffX; cli_setup_packet(cli); SSVAL(cli->outbuf,smb_vwv0,0xFF); SSVAL(cli->outbuf,smb_vwv2,0); /* no additional info */ cli_send_smb(cli); if (!cli_receive_smb(cli)) return False; return CVAL(cli->inbuf,smb_rcls) == 0; } /**************************************************************************** send a tconX ****************************************************************************/ BOOL cli_send_tconX(struct cli_state *cli, char *share, char *dev, char *pass, int passlen) { fstring fullshare, pword, dos_pword; char *p; memset(cli->outbuf,'\0',smb_size); memset(cli->inbuf,'\0',smb_size); fstrcpy(cli->share, share); /* in user level security don't send a password now */ if (cli->sec_mode & 1) { passlen = 1; pass = ""; } if ((cli->sec_mode & 2) && *pass && passlen != 24) { /* * Non-encrypted passwords - convert to DOS codepage before encryption. */ passlen = 24; fstrcpy(dos_pword,pass); unix_to_dos(dos_pword,True); SMBencrypt((uchar *)dos_pword,(uchar *)cli->cryptkey,(uchar *)pword); } else { if(!(cli->sec_mode & 2)) { /* * Non-encrypted passwords - convert to DOS codepage before using. */ fstrcpy(pword,pass); unix_to_dos(pword,True); } else { memcpy(pword, pass, passlen); } } slprintf(fullshare, sizeof(fullshare)-1, "\\\\%s\\%s", cli->desthost, share); unix_to_dos(fullshare, True); strupper(fullshare); set_message(cli->outbuf,4, 2 + strlen(fullshare) + passlen + strlen(dev),True); CVAL(cli->outbuf,smb_com) = SMBtconX; cli_setup_packet(cli); SSVAL(cli->outbuf,smb_vwv0,0xFF); SSVAL(cli->outbuf,smb_vwv3,passlen); p = smb_buf(cli->outbuf); memcpy(p,pword,passlen); p += passlen; fstrcpy(p,fullshare); p = skip_string(p,1); pstrcpy(p,dev); unix_to_dos(p,True); SCVAL(cli->inbuf,smb_rcls, 1); cli_send_smb(cli); if (!cli_receive_smb(cli)) return False; if (CVAL(cli->inbuf,smb_rcls) != 0) { return False; } fstrcpy(cli->dev, "A:"); if (cli->protocol >= PROTOCOL_NT1) { fstrcpy(cli->dev, smb_buf(cli->inbuf)); } if (strcasecmp(share,"IPC$")==0) { fstrcpy(cli->dev, "IPC"); } /* only grab the device if we have a recent protocol level */ if (cli->protocol >= PROTOCOL_NT1 && smb_buflen(cli->inbuf) == 3) { /* almost certainly win95 - enable bug fixes */ cli->win95 = True; } cli->cnum = SVAL(cli->inbuf,smb_tid); return True; } /**************************************************************************** send a tree disconnect ****************************************************************************/ BOOL cli_tdis(struct cli_state *cli) { memset(cli->outbuf,'\0',smb_size); set_message(cli->outbuf,0,0,True); CVAL(cli->outbuf,smb_com) = SMBtdis; SSVAL(cli->outbuf,smb_tid,cli->cnum); cli_setup_packet(cli); cli_send_smb(cli); if (!cli_receive_smb(cli)) return False; return CVAL(cli->inbuf,smb_rcls) == 0; } /**************************************************************************** send a negprot command ****************************************************************************/ BOOL cli_negprot(struct cli_state *cli) { char *p; int numprots; int plength; memset(cli->outbuf,'\0',smb_size); /* setup the protocol strings */ for (plength=0,numprots=0; prots[numprots].name && prots[numprots].prot<=cli->protocol; numprots++) plength += strlen(prots[numprots].name)+2; set_message(cli->outbuf,0,plength,True); p = smb_buf(cli->outbuf); for (numprots=0; prots[numprots].name && prots[numprots].prot<=cli->protocol; numprots++) { *p++ = 2; pstrcpy(p,prots[numprots].name); unix_to_dos(p,True); p += strlen(p) + 1; } CVAL(cli->outbuf,smb_com) = SMBnegprot; cli_setup_packet(cli); CVAL(smb_buf(cli->outbuf),0) = 2; cli_send_smb(cli); if (!cli_receive_smb(cli)) return False; show_msg(cli->inbuf); if (CVAL(cli->inbuf,smb_rcls) != 0 || ((int)SVAL(cli->inbuf,smb_vwv0) >= numprots)) { return(False); } cli->protocol = prots[SVAL(cli->inbuf,smb_vwv0)].prot; if (cli->protocol >= PROTOCOL_NT1) { /* NT protocol */ cli->sec_mode = CVAL(cli->inbuf,smb_vwv1); cli->max_mux = SVAL(cli->inbuf, smb_vwv1+1); cli->max_xmit = IVAL(cli->inbuf,smb_vwv3+1); cli->sesskey = IVAL(cli->inbuf,smb_vwv7+1); cli->serverzone = SVALS(cli->inbuf,smb_vwv15+1); cli->serverzone *= 60; /* this time arrives in real GMT */ cli->servertime = interpret_long_date(cli->inbuf+smb_vwv11+1); memcpy(cli->cryptkey,smb_buf(cli->inbuf),8); cli->capabilities = IVAL(cli->inbuf,smb_vwv9+1); if (cli->capabilities & 1) { cli->readbraw_supported = True; cli->writebraw_supported = True; } } else if (cli->protocol >= PROTOCOL_LANMAN1) { cli->sec_mode = SVAL(cli->inbuf,smb_vwv1); cli->max_xmit = SVAL(cli->inbuf,smb_vwv2); cli->sesskey = IVAL(cli->inbuf,smb_vwv6); cli->serverzone = SVALS(cli->inbuf,smb_vwv10); cli->serverzone *= 60; /* this time is converted to GMT by make_unix_date */ cli->servertime = make_unix_date(cli->inbuf+smb_vwv8); cli->readbraw_supported = ((SVAL(cli->inbuf,smb_vwv5) & 0x1) != 0); cli->writebraw_supported = ((SVAL(cli->inbuf,smb_vwv5) & 0x2) != 0); memcpy(cli->cryptkey,smb_buf(cli->inbuf),8); } else { /* the old core protocol */ cli->sec_mode = 0; cli->serverzone = TimeDiff(time(NULL)); } cli->max_xmit = MIN(cli->max_xmit, CLI_BUFFER_SIZE); return True; } /**************************************************************************** send a session request. see rfc1002.txt 4.3 and 4.3.2 ****************************************************************************/ BOOL cli_session_request(struct cli_state *cli, struct nmb_name *calling, struct nmb_name *called) { char *p; int len = 4; extern pstring user_socket_options; /* send a session request (RFC 1002) */ memcpy(&(cli->calling), calling, sizeof(*calling)); memcpy(&(cli->called ), called , sizeof(*called )); /* put in the destination name */ p = cli->outbuf+len; name_mangle(cli->called .name, p, cli->called .name_type); len += name_len(p); /* and my name */ p = cli->outbuf+len; name_mangle(cli->calling.name, p, cli->calling.name_type); len += name_len(p); /* setup the packet length */ _smb_setlen(cli->outbuf,len); CVAL(cli->outbuf,0) = 0x81; #ifdef WITH_SSL retry: #endif /* WITH_SSL */ cli_send_smb(cli); DEBUG(5,("Sent session request\n")); if (!cli_receive_smb(cli)) return False; if (CVAL(cli->inbuf,0) == 0x84) { /* C. Hoch 9/14/95 Start */ /* For information, here is the response structure. * We do the byte-twiddling to for portability. struct RetargetResponse{ unsigned char type; unsigned char flags; int16 length; int32 ip_addr; int16 port; }; */ int port = (CVAL(cli->inbuf,8)<<8)+CVAL(cli->inbuf,9); /* SESSION RETARGET */ putip((char *)&cli->dest_ip,cli->inbuf+4); cli->fd = open_socket_out(SOCK_STREAM, &cli->dest_ip, port, LONG_CONNECT_TIMEOUT); if (cli->fd == -1) return False; DEBUG(3,("Retargeted\n")); set_socket_options(cli->fd,user_socket_options); /* Try again */ { static int depth; BOOL ret; if (depth > 4) { DEBUG(0,("Retarget recursion - failing\n")); return False; } depth++; ret = cli_session_request(cli, calling, called); depth--; return ret; } } /* C. Hoch 9/14/95 End */ #ifdef WITH_SSL if (CVAL(cli->inbuf,0) == 0x83 && CVAL(cli->inbuf,4) == 0x8e){ /* use ssl */ if (!sslutil_fd_is_ssl(cli->fd)){ if (sslutil_connect(cli->fd) == 0) goto retry; } } #endif /* WITH_SSL */ if (CVAL(cli->inbuf,0) != 0x82) { /* This is the wrong place to put the error... JRA. */ cli->rap_error = CVAL(cli->inbuf,4); return False; } return(True); } /**************************************************************************** open the client sockets ****************************************************************************/ BOOL cli_connect(struct cli_state *cli, const char *host, struct in_addr *ip) { extern struct in_addr ipzero; extern pstring user_socket_options; fstrcpy(cli->desthost, host); if (!ip || ip_equal(*ip, ipzero)) { if (!resolve_name( cli->desthost, &cli->dest_ip, 0x20)) { return False; } if (ip) *ip = cli->dest_ip; } else { cli->dest_ip = *ip; } if (cli->port == 0) cli->port = 139; /* Set to default */ cli->fd = open_socket_out(SOCK_STREAM, &cli->dest_ip, cli->port, cli->timeout); if (cli->fd == -1) return False; set_socket_options(cli->fd,user_socket_options); return True; } /**************************************************************************** re-establishes a connection ****************************************************************************/ BOOL cli_reestablish_connection(struct cli_state *cli) { struct nmb_name calling; struct nmb_name called; fstring dest_host; fstring share; fstring dev; BOOL do_tcon = False; int oldfd = cli->fd; if (!cli->initialised || cli->fd == -1) { DEBUG(3,("cli_reestablish_connection: not connected\n")); return False; } /* copy the parameters necessary to re-establish the connection */ if (cli->cnum != 0) { fstrcpy(share, cli->share); fstrcpy(dev , cli->dev); do_tcon = True; } memcpy(&called , &(cli->called ), sizeof(called )); memcpy(&calling, &(cli->calling), sizeof(calling)); fstrcpy(dest_host, cli->full_dest_host_name); DEBUG(5,("cli_reestablish_connection: %s connecting to %s (ip %s) - %s [%s]\n", nmb_namestr(&calling), nmb_namestr(&called), inet_ntoa(cli->dest_ip), cli->user_name, cli->domain)); cli->fd = -1; if (cli_establish_connection(cli, dest_host, &cli->dest_ip, &calling, &called, share, dev, False, do_tcon)) { if (cli->fd != oldfd) { if (dup2(cli->fd, oldfd) == oldfd) { close(cli->fd); } } return True; } return False; } /**************************************************************************** establishes a connection right up to doing tconX, reading in a password. ****************************************************************************/ BOOL cli_establish_connection(struct cli_state *cli, char *dest_host, struct in_addr *dest_ip, struct nmb_name *calling, struct nmb_name *called, char *service, char *service_type, BOOL do_shutdown, BOOL do_tcon) { DEBUG(5,("cli_establish_connection: %s connecting to %s (%s) - %s [%s]\n", nmb_namestr(calling), nmb_namestr(called), inet_ntoa(*dest_ip), cli->user_name, cli->domain)); /* establish connection */ if ((!cli->initialised)) { return False; } if (cli->fd == -1) { if (!cli_connect(cli, dest_host, dest_ip)) { DEBUG(1,("cli_establish_connection: failed to connect to %s (%s)\n", nmb_namestr(calling), inet_ntoa(*dest_ip))); return False; } } if (!cli_session_request(cli, calling, called)) { DEBUG(1,("failed session request\n")); if (do_shutdown) cli_shutdown(cli); return False; } if (!cli_negprot(cli)) { DEBUG(1,("failed negprot\n")); if (do_shutdown) cli_shutdown(cli); return False; } if (cli->pwd.cleartext || cli->pwd.null_pwd) { fstring passwd; int pass_len; if (cli->pwd.null_pwd) { /* attempt null session */ passwd[0] = 0; pass_len = 1; } else { /* attempt clear-text session */ pwd_get_cleartext(&(cli->pwd), passwd); pass_len = strlen(passwd); } /* attempt clear-text session */ if (!cli_session_setup(cli, cli->user_name, passwd, pass_len, NULL, 0, cli->domain)) { DEBUG(1,("failed session setup\n")); if (do_shutdown) { cli_shutdown(cli); } return False; } if (do_tcon) { if (!cli_send_tconX(cli, service, service_type, (char*)passwd, strlen(passwd))) { DEBUG(1,("failed tcon_X\n")); if (do_shutdown) { cli_shutdown(cli); } return False; } } } else { /* attempt encrypted session */ unsigned char nt_sess_pwd[24]; unsigned char lm_sess_pwd[24]; /* creates (storing a copy of) and then obtains a 24 byte password OWF */ pwd_make_lm_nt_owf(&(cli->pwd), cli->cryptkey); pwd_get_lm_nt_owf(&(cli->pwd), lm_sess_pwd, nt_sess_pwd); /* attempt encrypted session */ if (!cli_session_setup(cli, cli->user_name, (char*)lm_sess_pwd, sizeof(lm_sess_pwd), (char*)nt_sess_pwd, sizeof(nt_sess_pwd), cli->domain)) { DEBUG(1,("failed session setup\n")); if (do_shutdown) cli_shutdown(cli); return False; } if (do_tcon) { if (!cli_send_tconX(cli, service, service_type, (char*)nt_sess_pwd, sizeof(nt_sess_pwd))) { DEBUG(1,("failed tcon_X\n")); if (do_shutdown) cli_shutdown(cli); return False; } } } if (do_shutdown) cli_shutdown(cli); return True; } /**************************************************************************** Attempt a NetBIOS session request, falling back to *SMBSERVER if needed. ****************************************************************************/ BOOL attempt_netbios_session_request(struct cli_state *cli, char *srchost, char *desthost, struct in_addr *pdest_ip) { struct nmb_name calling, called; make_nmb_name(&calling, srchost, 0x0); /* * If the called name is an IP address * then use *SMBSERVER immediately. */ if(is_ipaddress(desthost)) make_nmb_name(&called, "*SMBSERVER", 0x20); else make_nmb_name(&called, desthost, 0x20); if (!cli_session_request(cli, &calling, &called)) { struct nmb_name smbservername; make_nmb_name(&smbservername , "*SMBSERVER", 0x20); /* * If the name wasn't *SMBSERVER then * try with *SMBSERVER if the first name fails. */ if (nmb_name_equal(&called, &smbservername)) { /* * The name used was *SMBSERVER, don't bother with another name. */ DEBUG(0,("attempt_netbios_session_request: %s rejected the session for name *SMBSERVER \ with error %s.\n", desthost, cli_errstr(cli) )); cli_shutdown(cli); return False; } cli_shutdown(cli); if (!cli_initialise(cli) || !cli_connect(cli, desthost, pdest_ip) || !cli_session_request(cli, &calling, &smbservername)) { DEBUG(0,("attempt_netbios_session_request: %s rejected the session for \ name *SMBSERVER with error %s\n", desthost, cli_errstr(cli) )); cli_shutdown(cli); return False; } } return True; }