diff options
Diffstat (limited to 'fs/cifs/connect.c')
-rw-r--r-- | fs/cifs/connect.c | 3064 |
1 files changed, 3064 insertions, 0 deletions
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c new file mode 100644 index 00000000000..40470b9d547 --- /dev/null +++ b/fs/cifs/connect.c @@ -0,0 +1,3064 @@ +/* + * fs/cifs/connect.c + * + * Copyright (C) International Business Machines Corp., 2002,2004 + * Author(s): Steve French (sfrench@us.ibm.com) + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/fs.h> +#include <linux/net.h> +#include <linux/string.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/ipv6.h> +#include <linux/pagemap.h> +#include <linux/ctype.h> +#include <linux/utsname.h> +#include <linux/mempool.h> +#include <asm/uaccess.h> +#include <asm/processor.h> +#include "cifspdu.h" +#include "cifsglob.h" +#include "cifsproto.h" +#include "cifs_unicode.h" +#include "cifs_debug.h" +#include "cifs_fs_sb.h" +#include "ntlmssp.h" +#include "nterr.h" +#include "rfc1002pdu.h" + +#define CIFS_PORT 445 +#define RFC1001_PORT 139 + +extern void SMBencrypt(unsigned char *passwd, unsigned char *c8, + unsigned char *p24); +extern void SMBNTencrypt(unsigned char *passwd, unsigned char *c8, + unsigned char *p24); + +extern mempool_t *cifs_req_poolp; + +struct smb_vol { + char *username; + char *password; + char *domainname; + char *UNC; + char *UNCip; + char *in6_addr; /* ipv6 address as human readable form of in6_addr */ + char *iocharset; /* local code page for mapping to and from Unicode */ + char source_rfc1001_name[16]; /* netbios name of client */ + uid_t linux_uid; + gid_t linux_gid; + mode_t file_mode; + mode_t dir_mode; + unsigned rw:1; + unsigned retry:1; + unsigned intr:1; + unsigned setuids:1; + unsigned noperm:1; + unsigned no_psx_acl:1; /* set if posix acl support should be disabled */ + unsigned no_xattr:1; /* set if xattr (EA) support should be disabled*/ + unsigned server_ino:1; /* use inode numbers from server ie UniqueId */ + unsigned direct_io:1; + unsigned int rsize; + unsigned int wsize; + unsigned int sockopt; + unsigned short int port; +}; + +static int ipv4_connect(struct sockaddr_in *psin_server, + struct socket **csocket, + char * netb_name); +static int ipv6_connect(struct sockaddr_in6 *psin_server, + struct socket **csocket); + + + /* + * cifs tcp session reconnection + * + * mark tcp session as reconnecting so temporarily locked + * mark all smb sessions as reconnecting for tcp session + * reconnect tcp session + * wake up waiters on reconnection? - (not needed currently) + */ + +int +cifs_reconnect(struct TCP_Server_Info *server) +{ + int rc = 0; + struct list_head *tmp; + struct cifsSesInfo *ses; + struct cifsTconInfo *tcon; + struct mid_q_entry * mid_entry; + + spin_lock(&GlobalMid_Lock); + if(server->tcpStatus == CifsExiting) { + /* the demux thread will exit normally + next time through the loop */ + spin_unlock(&GlobalMid_Lock); + return rc; + } else + server->tcpStatus = CifsNeedReconnect; + spin_unlock(&GlobalMid_Lock); + server->maxBuf = 0; + + cFYI(1, ("Reconnecting tcp session ")); + + /* before reconnecting the tcp session, mark the smb session (uid) + and the tid bad so they are not used until reconnected */ + read_lock(&GlobalSMBSeslock); + list_for_each(tmp, &GlobalSMBSessionList) { + ses = list_entry(tmp, struct cifsSesInfo, cifsSessionList); + if (ses->server) { + if (ses->server == server) { + ses->status = CifsNeedReconnect; + ses->ipc_tid = 0; + } + } + /* else tcp and smb sessions need reconnection */ + } + list_for_each(tmp, &GlobalTreeConnectionList) { + tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList); + if((tcon) && (tcon->ses) && (tcon->ses->server == server)) { + tcon->tidStatus = CifsNeedReconnect; + } + } + read_unlock(&GlobalSMBSeslock); + /* do not want to be sending data on a socket we are freeing */ + down(&server->tcpSem); + if(server->ssocket) { + cFYI(1,("State: 0x%x Flags: 0x%lx", server->ssocket->state, + server->ssocket->flags)); + server->ssocket->ops->shutdown(server->ssocket,SEND_SHUTDOWN); + cFYI(1,("Post shutdown state: 0x%x Flags: 0x%lx", server->ssocket->state, + server->ssocket->flags)); + sock_release(server->ssocket); + server->ssocket = NULL; + } + + spin_lock(&GlobalMid_Lock); + list_for_each(tmp, &server->pending_mid_q) { + mid_entry = list_entry(tmp, struct + mid_q_entry, + qhead); + if(mid_entry) { + if(mid_entry->midState == MID_REQUEST_SUBMITTED) { + /* Mark other intransit requests as needing retry so + we do not immediately mark the session bad again + (ie after we reconnect below) as they timeout too */ + mid_entry->midState = MID_RETRY_NEEDED; + } + } + } + spin_unlock(&GlobalMid_Lock); + up(&server->tcpSem); + + while ((server->tcpStatus != CifsExiting) && (server->tcpStatus != CifsGood)) + { + if(server->protocolType == IPV6) { + rc = ipv6_connect(&server->addr.sockAddr6,&server->ssocket); + } else { + rc = ipv4_connect(&server->addr.sockAddr, + &server->ssocket, + server->workstation_RFC1001_name); + } + if(rc) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(3 * HZ); + } else { + atomic_inc(&tcpSesReconnectCount); + spin_lock(&GlobalMid_Lock); + if(server->tcpStatus != CifsExiting) + server->tcpStatus = CifsGood; + spin_unlock(&GlobalMid_Lock); + /* atomic_set(&server->inFlight,0);*/ + wake_up(&server->response_q); + } + } + return rc; +} + +static int +cifs_demultiplex_thread(struct TCP_Server_Info *server) +{ + int length; + unsigned int pdu_length, total_read; + struct smb_hdr *smb_buffer = NULL; + struct msghdr smb_msg; + struct kvec iov; + struct socket *csocket = server->ssocket; + struct list_head *tmp; + struct cifsSesInfo *ses; + struct task_struct *task_to_wake = NULL; + struct mid_q_entry *mid_entry; + char *temp; + + daemonize("cifsd"); + allow_signal(SIGKILL); + current->flags |= PF_MEMALLOC; + server->tsk = current; /* save process info to wake at shutdown */ + cFYI(1, ("Demultiplex PID: %d", current->pid)); + write_lock(&GlobalSMBSeslock); + atomic_inc(&tcpSesAllocCount); + length = tcpSesAllocCount.counter; + write_unlock(&GlobalSMBSeslock); + if(length > 1) { + mempool_resize(cifs_req_poolp, + length + cifs_min_rcv, + GFP_KERNEL); + } + + while (server->tcpStatus != CifsExiting) { + if (smb_buffer == NULL) + smb_buffer = cifs_buf_get(); + else + memset(smb_buffer, 0, sizeof (struct smb_hdr)); + + if (smb_buffer == NULL) { + cERROR(1,("Can not get memory for SMB response")); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ * 3); /* give system time to free memory */ + continue; + } + iov.iov_base = smb_buffer; + iov.iov_len = 4; + smb_msg.msg_control = NULL; + smb_msg.msg_controllen = 0; + length = + kernel_recvmsg(csocket, &smb_msg, + &iov, 1, 4, 0 /* BB see socket.h flags */); + + if(server->tcpStatus == CifsExiting) { + break; + } else if (server->tcpStatus == CifsNeedReconnect) { + cFYI(1,("Reconnecting after server stopped responding")); + cifs_reconnect(server); + cFYI(1,("call to reconnect done")); + csocket = server->ssocket; + continue; + } else if ((length == -ERESTARTSYS) || (length == -EAGAIN)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); /* minimum sleep to prevent looping + allowing socket to clear and app threads to set + tcpStatus CifsNeedReconnect if server hung */ + continue; + } else if (length <= 0) { + if(server->tcpStatus == CifsNew) { + cFYI(1,("tcp session abended prematurely (after SMBnegprot)")); + /* some servers kill tcp session rather than returning + smb negprot error in which case reconnecting here is + not going to help - return error to mount */ + break; + } + if(length == -EINTR) { + cFYI(1,("cifsd thread killed")); + break; + } + cFYI(1,("Reconnecting after unexpected peek error %d",length)); + cifs_reconnect(server); + csocket = server->ssocket; + wake_up(&server->response_q); + continue; + } else if (length > 3) { + pdu_length = ntohl(smb_buffer->smb_buf_length); + /* Only read pdu_length after below checks for too short (due + to e.g. int overflow) and too long ie beyond end of buf */ + cFYI(1,("rfc1002 length(big endian)0x%x)", pdu_length+4)); + + temp = (char *) smb_buffer; + if (temp[0] == (char) RFC1002_SESSION_KEEP_ALIVE) { + cFYI(0,("Received 4 byte keep alive packet")); + } else if (temp[0] == (char) RFC1002_POSITIVE_SESSION_RESPONSE) { + cFYI(1,("Good RFC 1002 session rsp")); + } else if (temp[0] == (char)RFC1002_NEGATIVE_SESSION_RESPONSE) { + /* we get this from Windows 98 instead of error on SMB negprot response */ + cFYI(1,("Negative RFC 1002 Session Response Error 0x%x)",temp[4])); + if(server->tcpStatus == CifsNew) { + /* if nack on negprot (rather than + ret of smb negprot error) reconnecting + not going to help, ret error to mount */ + break; + } else { + /* give server a second to + clean up before reconnect attempt */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + /* always try 445 first on reconnect + since we get NACK on some if we ever + connected to port 139 (the NACK is + since we do not begin with RFC1001 + session initialize frame) */ + server->addr.sockAddr.sin_port = htons(CIFS_PORT); + cifs_reconnect(server); + csocket = server->ssocket; + wake_up(&server->response_q); + continue; + } + } else if (temp[0] != (char) 0) { + cERROR(1,("Unknown RFC 1002 frame")); + cifs_dump_mem(" Received Data: ", temp, length); + cifs_reconnect(server); + csocket = server->ssocket; + continue; + } else { + if((pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) + || (pdu_length < sizeof (struct smb_hdr) - 1 - 4)) { + cERROR(1, + ("Invalid size SMB length %d and pdu_length %d", + length, pdu_length+4)); + cifs_reconnect(server); + csocket = server->ssocket; + wake_up(&server->response_q); + continue; + } else { /* length ok */ + length = 0; + iov.iov_base = 4 + (char *)smb_buffer; + iov.iov_len = pdu_length; + for (total_read = 0; + total_read < pdu_length; + total_read += length) { + length = kernel_recvmsg(csocket, &smb_msg, + &iov, 1, + pdu_length - total_read, 0); + if (length == 0) { + cERROR(1, + ("Zero length receive when expecting %d ", + pdu_length - total_read)); + cifs_reconnect(server); + csocket = server->ssocket; + wake_up(&server->response_q); + continue; + } + } + length += 4; /* account for rfc1002 hdr */ + } + + dump_smb(smb_buffer, length); + if (checkSMB + (smb_buffer, smb_buffer->Mid, total_read+4)) { + cERROR(1, ("Bad SMB Received ")); + continue; + } + + task_to_wake = NULL; + spin_lock(&GlobalMid_Lock); + list_for_each(tmp, &server->pending_mid_q) { + mid_entry = list_entry(tmp, struct + mid_q_entry, + qhead); + + if ((mid_entry->mid == smb_buffer->Mid) && (mid_entry->midState == MID_REQUEST_SUBMITTED)) { + cFYI(1, + (" Mid 0x%x matched - waking up ",mid_entry->mid)); + task_to_wake = mid_entry->tsk; + mid_entry->resp_buf = + smb_buffer; + mid_entry->midState = + MID_RESPONSE_RECEIVED; + } + } + spin_unlock(&GlobalMid_Lock); + if (task_to_wake) { + smb_buffer = NULL; /* will be freed by users thread after he is done */ + wake_up_process(task_to_wake); + } else if (is_valid_oplock_break(smb_buffer) == FALSE) { + cERROR(1, ("No task to wake, unknown frame rcvd!")); + cifs_dump_mem("Received Data is: ",temp,sizeof(struct smb_hdr)); + } + } + } else { + cFYI(1, + ("Frame less than four bytes received %d bytes long.", + length)); + cifs_reconnect(server); + csocket = server->ssocket; + wake_up(&server->response_q); + continue; + } + } + spin_lock(&GlobalMid_Lock); + server->tcpStatus = CifsExiting; + server->tsk = NULL; + atomic_set(&server->inFlight, 0); + spin_unlock(&GlobalMid_Lock); + /* Although there should not be any requests blocked on + this queue it can not hurt to be paranoid and try to wake up requests + that may haven been blocked when more than 50 at time were on the wire + to the same server - they now will see the session is in exit state + and get out of SendReceive. */ + wake_up_all(&server->request_q); + /* give those requests time to exit */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/8); + + if(server->ssocket) { + sock_release(csocket); + server->ssocket = NULL; + } + if (smb_buffer) /* buffer usually freed in free_mid - need to free it on error or exit */ + cifs_buf_release(smb_buffer); + + read_lock(&GlobalSMBSeslock); + if (list_empty(&server->pending_mid_q)) { + /* loop through server session structures attached to this and mark them dead */ + list_for_each(tmp, &GlobalSMBSessionList) { + ses = + list_entry(tmp, struct cifsSesInfo, + cifsSessionList); + if (ses->server == server) { + ses->status = CifsExiting; + ses->server = NULL; + } + } + read_unlock(&GlobalSMBSeslock); + } else { + spin_lock(&GlobalMid_Lock); + list_for_each(tmp, &server->pending_mid_q) { + mid_entry = list_entry(tmp, struct mid_q_entry, qhead); + if (mid_entry->midState == MID_REQUEST_SUBMITTED) { + cFYI(1, + (" Clearing Mid 0x%x - waking up ",mid_entry->mid)); + task_to_wake = mid_entry->tsk; + if(task_to_wake) { + wake_up_process(task_to_wake); + } + } + } + spin_unlock(&GlobalMid_Lock); + read_unlock(&GlobalSMBSeslock); + set_current_state(TASK_INTERRUPTIBLE); + /* 1/8th of sec is more than enough time for them to exit */ + schedule_timeout(HZ/8); + } + + if (list_empty(&server->pending_mid_q)) { + /* mpx threads have not exited yet give them + at least the smb send timeout time for long ops */ + cFYI(1, ("Wait for exit from demultiplex thread")); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(46 * HZ); + /* if threads still have not exited they are probably never + coming home not much else we can do but free the memory */ + } + kfree(server); + + write_lock(&GlobalSMBSeslock); + atomic_dec(&tcpSesAllocCount); + length = tcpSesAllocCount.counter; + write_unlock(&GlobalSMBSeslock); + if(length > 0) { + mempool_resize(cifs_req_poolp, + length + cifs_min_rcv, + GFP_KERNEL); + } + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/4); + return 0; +} + +static void * +cifs_kcalloc(size_t size, unsigned int __nocast type) +{ + void *addr; + addr = kmalloc(size, type); + if (addr) + memset(addr, 0, size); + return addr; +} + +static int +cifs_parse_mount_options(char *options, const char *devname,struct smb_vol *vol) +{ + char *value; + char *data; + unsigned int temp_len, i, j; + char separator[2]; + + separator[0] = ','; + separator[1] = 0; + + memset(vol->source_rfc1001_name,0x20,15); + for(i=0;i < strnlen(system_utsname.nodename,15);i++) { + /* does not have to be a perfect mapping since the field is + informational, only used for servers that do not support + port 445 and it can be overridden at mount time */ + vol->source_rfc1001_name[i] = toupper(system_utsname.nodename[i]); + } + vol->source_rfc1001_name[15] = 0; + + vol->linux_uid = current->uid; /* current->euid instead? */ + vol->linux_gid = current->gid; + vol->dir_mode = S_IRWXUGO; + /* 2767 perms indicate mandatory locking support */ + vol->file_mode = S_IALLUGO & ~(S_ISUID | S_IXGRP); + + /* vol->retry default is 0 (i.e. "soft" limited retry not hard retry) */ + vol->rw = TRUE; + + if (!options) + return 1; + + if(strncmp(options,"sep=",4) == 0) { + if(options[4] != 0) { + separator[0] = options[4]; + options += 5; + } else { + cFYI(1,("Null separator not allowed")); + } + } + + while ((data = strsep(&options, separator)) != NULL) { + if (!*data) + continue; + if ((value = strchr(data, '=')) != NULL) + *value++ = '\0'; + + if (strnicmp(data, "user_xattr",10) == 0) {/*parse before user*/ + vol->no_xattr = 0; + } else if (strnicmp(data, "nouser_xattr",12) == 0) { + vol->no_xattr = 1; + } else if (strnicmp(data, "user", 4) == 0) { + if (!value || !*value) { + printk(KERN_WARNING + "CIFS: invalid or missing username\n"); + return 1; /* needs_arg; */ + } + if (strnlen(value, 200) < 200) { + vol->username = value; + } else { + printk(KERN_WARNING "CIFS: username too long\n"); + return 1; + } + } else if (strnicmp(data, "pass", 4) == 0) { + if (!value) { + vol->password = NULL; + continue; + } else if(value[0] == 0) { + /* check if string begins with double comma + since that would mean the password really + does start with a comma, and would not + indicate an empty string */ + if(value[1] != separator[0]) { + vol->password = NULL; + continue; + } + } + temp_len = strlen(value); + /* removed password length check, NTLM passwords + can be arbitrarily long */ + + /* if comma in password, the string will be + prematurely null terminated. Commas in password are + specified across the cifs mount interface by a double + comma ie ,, and a comma used as in other cases ie ',' + as a parameter delimiter/separator is single and due + to the strsep above is temporarily zeroed. */ + + /* NB: password legally can have multiple commas and + the only illegal character in a password is null */ + + if ((value[temp_len] == 0) && (value[temp_len+1] == separator[0])) { + /* reinsert comma */ + value[temp_len] = separator[0]; + temp_len+=2; /* move after the second comma */ + while(value[temp_len] != 0) { + if (value[temp_len] == separator[0]) { + if (value[temp_len+1] == separator[0]) { + temp_len++; /* skip second comma */ + } else { + /* single comma indicating start + of next parm */ + break; + } + } + temp_len++; + } + if(value[temp_len] == 0) { + options = NULL; + } else { + value[temp_len] = 0; + /* point option to start of next parm */ + options = value + temp_len + 1; + } + /* go from value to value + temp_len condensing + double commas to singles. Note that this ends up + allocating a few bytes too many, which is ok */ + vol->password = cifs_kcalloc(temp_len, GFP_KERNEL); + for(i=0,j=0;i<temp_len;i++,j++) { + vol->password[j] = value[i]; + if(value[i] == separator[0] && value[i+1] == separator[0]) { + /* skip second comma */ + i++; + } + } + vol->password[j] = 0; + } else { + vol->password = cifs_kcalloc(temp_len + 1, GFP_KERNEL); + strcpy(vol->password, value); + } + } else if (strnicmp(data, "ip", 2) == 0) { + if (!value || !*value) { + vol->UNCip = NULL; + } else if (strnlen(value, 35) < 35) { + vol->UNCip = value; + } else { + printk(KERN_WARNING "CIFS: ip address too long\n"); + return 1; + } + } else if ((strnicmp(data, "unc", 3) == 0) + || (strnicmp(data, "target", 6) == 0) + || (strnicmp(data, "path", 4) == 0)) { + if (!value || !*value) { + printk(KERN_WARNING + "CIFS: invalid path to network resource\n"); + return 1; /* needs_arg; */ + } + if ((temp_len = strnlen(value, 300)) < 300) { + vol->UNC = kmalloc(temp_len+1,GFP_KERNEL); + if(vol->UNC == NULL) + return 1; + strcpy(vol->UNC,value); + if (strncmp(vol->UNC, "//", 2) == 0) { + vol->UNC[0] = '\\'; + vol->UNC[1] = '\\'; + } else if (strncmp(vol->UNC, "\\\\", 2) != 0) { + printk(KERN_WARNING + "CIFS: UNC Path does not begin with // or \\\\ \n"); + return 1; + } + } else { + printk(KERN_WARNING "CIFS: UNC name too long\n"); + return 1; + } + } else if ((strnicmp(data, "domain", 3) == 0) + || (strnicmp(data, "workgroup", 5) == 0)) { + if (!value || !*value) { + printk(KERN_WARNING "CIFS: invalid domain name\n"); + return 1; /* needs_arg; */ + } + /* BB are there cases in which a comma can be valid in + a domain name and need special handling? */ + if (strnlen(value, 65) < 65) { + vol->domainname = value; + cFYI(1, ("Domain name set")); + } else { + printk(KERN_WARNING "CIFS: domain name too long\n"); + return 1; + } + } else if (strnicmp(data, "iocharset", 9) == 0) { + if (!value || !*value) { + printk(KERN_WARNING "CIFS: invalid iocharset specified\n"); + return 1; /* needs_arg; */ + } + if (strnlen(value, 65) < 65) { + if(strnicmp(value,"default",7)) + vol->iocharset = value; + /* if iocharset not set load_nls_default used by caller */ + cFYI(1, ("iocharset set to %s",value)); + } else { + printk(KERN_WARNING "CIFS: iocharset name too long.\n"); + return 1; + } + } else if (strnicmp(data, "uid", 3) == 0) { + if (value && *value) { + vol->linux_uid = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "gid", 3) == 0) { + if (value && *value) { + vol->linux_gid = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "file_mode", 4) == 0) { + if (value && *value) { + vol->file_mode = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "dir_mode", 4) == 0) { + if (value && *value) { + vol->dir_mode = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "dirmode", 4) == 0) { + if (value && *value) { + vol->dir_mode = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "port", 4) == 0) { + if (value && *value) { + vol->port = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "rsize", 5) == 0) { + if (value && *value) { + vol->rsize = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "wsize", 5) == 0) { + if (value && *value) { + vol->wsize = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "sockopt", 5) == 0) { + if (value && *value) { + vol->sockopt = + simple_strtoul(value, &value, 0); + } + } else if (strnicmp(data, "netbiosname", 4) == 0) { + if (!value || !*value || (*value == ' ')) { + cFYI(1,("invalid (empty) netbiosname specified")); + } else { + memset(vol->source_rfc1001_name,0x20,15); + for(i=0;i<15;i++) { + /* BB are there cases in which a comma can be + valid in this workstation netbios name (and need + special handling)? */ + + /* We do not uppercase netbiosname for user */ + if (value[i]==0) + break; + else + vol->source_rfc1001_name[i] = value[i]; + } + /* The string has 16th byte zero still from + set at top of the function */ + if((i==15) && (value[i] != 0)) + printk(KERN_WARNING "CIFS: netbiosname longer than 15 and was truncated.\n"); + } + } else if (strnicmp(data, "credentials", 4) == 0) { + /* ignore */ + } else if (strnicmp(data, "version", 3) == 0) { + /* ignore */ + } else if (strnicmp(data, "guest",5) == 0) { + /* ignore */ + } else if (strnicmp(data, "rw", 2) == 0) { + vol->rw = TRUE; + } else if ((strnicmp(data, "suid", 4) == 0) || + (strnicmp(data, "nosuid", 6) == 0) || + (strnicmp(data, "exec", 4) == 0) || + (strnicmp(data, "noexec", 6) == 0) || + (strnicmp(data, "nodev", 5) == 0) || + (strnicmp(data, "noauto", 6) == 0) || + (strnicmp(data, "dev", 3) == 0)) { + /* The mount tool or mount.cifs helper (if present) + uses these opts to set flags, and the flags are read + by the kernel vfs layer before we get here (ie + before read super) so there is no point trying to + parse these options again and set anything and it + is ok to just ignore them */ + continue; + } else if (strnicmp(data, "ro", 2) == 0) { + vol->rw = FALSE; + } else if (strnicmp(data, "hard", 4) == 0) { + vol->retry = 1; + } else if (strnicmp(data, "soft", 4) == 0) { + vol->retry = 0; + } else if (strnicmp(data, "perm", 4) == 0) { + vol->noperm = 0; + } else if (strnicmp(data, "noperm", 6) == 0) { + vol->noperm = 1; + } else if (strnicmp(data, "setuids", 7) == 0) { + vol->setuids = 1; + } else if (strnicmp(data, "nosetuids", 9) == 0) { + vol->setuids = 0; + } else if (strnicmp(data, "nohard", 6) == 0) { + vol->retry = 0; + } else if (strnicmp(data, "nosoft", 6) == 0) { + vol->retry = 1; + } else if (strnicmp(data, "nointr", 6) == 0) { + vol->intr = 0; + } else if (strnicmp(data, "intr", 4) == 0) { + vol->intr = 1; + } else if (strnicmp(data, "serverino",7) == 0) { + vol->server_ino = 1; + } else if (strnicmp(data, "noserverino",9) == 0) { + vol->server_ino = 0; + } else if (strnicmp(data, "acl",3) == 0) { + vol->no_psx_acl = 0; + } else if (strnicmp(data, "noacl",5) == 0) { + vol->no_psx_acl = 1; + } else if (strnicmp(data, "direct",6) == 0) { + vol->direct_io = 1; + } else if (strnicmp(data, "forcedirectio",13) == 0) { + vol->direct_io = 1; + } else if (strnicmp(data, "in6_addr",8) == 0) { + if (!value || !*value) { + vol->in6_addr = NULL; + } else if (strnlen(value, 49) == 48) { + vol->in6_addr = value; + } else { + printk(KERN_WARNING "CIFS: ip v6 address not 48 characters long\n"); + return 1; + } + } else if (strnicmp(data, "noac", 4) == 0) { + printk(KERN_WARNING "CIFS: Mount option noac not supported. Instead set /proc/fs/cifs/LookupCacheEnabled to 0\n"); + } else + printk(KERN_WARNING "CIFS: Unknown mount option %s\n",data); + } + if (vol->UNC == NULL) { + if(devname == NULL) { + printk(KERN_WARNING "CIFS: Missing UNC name for mount target\n"); + return 1; + } + if ((temp_len = strnlen(devname, 300)) < 300) { + vol->UNC = kmalloc(temp_len+1,GFP_KERNEL); + if(vol->UNC == NULL) + return 1; + strcpy(vol->UNC,devname); + if (strncmp(vol->UNC, "//", 2) == 0) { + vol->UNC[0] = '\\'; + vol->UNC[1] = '\\'; + } else if (strncmp(vol->UNC, "\\\\", 2) != 0) { + printk(KERN_WARNING "CIFS: UNC Path does not begin with // or \\\\ \n"); + return 1; + } + } else { + printk(KERN_WARNING "CIFS: UNC name too long\n"); + return 1; + } + } + if(vol->UNCip == NULL) + vol->UNCip = &vol->UNC[2]; + + return 0; +} + +static struct cifsSesInfo * +cifs_find_tcp_session(struct in_addr * target_ip_addr, + struct in6_addr *target_ip6_addr, + char *userName, struct TCP_Server_Info **psrvTcp) +{ + struct list_head *tmp; + struct cifsSesInfo *ses; + *psrvTcp = NULL; + read_lock(&GlobalSMBSeslock); + + list_for_each(tmp, &GlobalSMBSessionList) { + ses = list_entry(tmp, struct cifsSesInfo, cifsSessionList); + if (ses->server) { + if((target_ip_addr && + (ses->server->addr.sockAddr.sin_addr.s_addr + == target_ip_addr->s_addr)) || (target_ip6_addr + && memcmp(&ses->server->addr.sockAddr6.sin6_addr, + target_ip6_addr,sizeof(*target_ip6_addr)))){ + /* BB lock server and tcp session and increment use count here?? */ + *psrvTcp = ses->server; /* found a match on the TCP session */ + /* BB check if reconnection needed */ + if (strncmp + (ses->userName, userName, + MAX_USERNAME_SIZE) == 0){ + read_unlock(&GlobalSMBSeslock); + return ses; /* found exact match on both tcp and SMB sessions */ + } + } + } + /* else tcp and smb sessions need reconnection */ + } + read_unlock(&GlobalSMBSeslock); + return NULL; +} + +static struct cifsTconInfo * +find_unc(__be32 new_target_ip_addr, char *uncName, char *userName) +{ + struct list_head *tmp; + struct cifsTconInfo *tcon; + + read_lock(&GlobalSMBSeslock); + list_for_each(tmp, &GlobalTreeConnectionList) { + cFYI(1, ("Next tcon - ")); + tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList); + if (tcon->ses) { + if (tcon->ses->server) { + cFYI(1, + (" old ip addr: %x == new ip %x ?", + tcon->ses->server->addr.sockAddr.sin_addr. + s_addr, new_target_ip_addr)); + if (tcon->ses->server->addr.sockAddr.sin_addr. + s_addr == new_target_ip_addr) { + /* BB lock tcon and server and tcp session and increment use count here? */ + /* found a match on the TCP session */ + /* BB check if reconnection needed */ + cFYI(1,("Matched ip, old UNC: %s == new: %s ?", + tcon->treeName, uncName)); + if (strncmp + (tcon->treeName, uncName, + MAX_TREE_SIZE) == 0) { + cFYI(1, + ("Matched UNC, old user: %s == new: %s ?", + tcon->treeName, uncName)); + if (strncmp + (tcon->ses->userName, + userName, + MAX_USERNAME_SIZE) == 0) { + read_unlock(&GlobalSMBSeslock); + return tcon;/* also matched user (smb session)*/ + } + } + } + } + } + } + read_unlock(&GlobalSMBSeslock); + return NULL; +} + +int +connect_to_dfs_path(int xid, struct cifsSesInfo *pSesInfo, + const char *old_path, const struct nls_table *nls_codepage) +{ + unsigned char *referrals = NULL; + unsigned int num_referrals; + int rc = 0; + + rc = get_dfs_path(xid, pSesInfo,old_path, nls_codepage, + &num_referrals, &referrals); + + /* BB Add in code to: if valid refrl, if not ip address contact + the helper that resolves tcp names, mount to it, try to + tcon to it unmount it if fail */ + + if(referrals) + kfree(referrals); + + return rc; +} + +int +get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, + const char *old_path, const struct nls_table *nls_codepage, + unsigned int *pnum_referrals, unsigned char ** preferrals) +{ + char *temp_unc; + int rc = 0; + + *pnum_referrals = 0; + + if (pSesInfo->ipc_tid == 0) { + temp_unc = kmalloc(2 /* for slashes */ + + strnlen(pSesInfo->serverName,SERVER_NAME_LEN_WITH_NULL * 2) + + 1 + 4 /* slash IPC$ */ + 2, + GFP_KERNEL); + if (temp_unc == NULL) + return -ENOMEM; + temp_unc[0] = '\\'; + temp_unc[1] = '\\'; + strcpy(temp_unc + 2, pSesInfo->serverName); + strcpy(temp_unc + 2 + strlen(pSesInfo->serverName), "\\IPC$"); + rc = CIFSTCon(xid, pSesInfo, temp_unc, NULL, nls_codepage); + cFYI(1, + ("CIFS Tcon rc = %d ipc_tid = %d", rc,pSesInfo->ipc_tid)); + kfree(temp_unc); + } + if (rc == 0) + rc = CIFSGetDFSRefer(xid, pSesInfo, old_path, preferrals, + pnum_referrals, nls_codepage); + + return rc; +} + +/* See RFC1001 section 14 on representation of Netbios names */ +static void rfc1002mangle(char * target,char * source, unsigned int length) +{ + unsigned int i,j; + + for(i=0,j=0;i<(length);i++) { + /* mask a nibble at a time and encode */ + target[j] = 'A' + (0x0F & (source[i] >> 4)); + target[j+1] = 'A' + (0x0F & source[i]); + j+=2; + } + +} + + +static int +ipv4_connect(struct sockaddr_in *psin_server, struct socket **csocket, + char * netbios_name) +{ + int rc = 0; + int connected = 0; + __be16 orig_port = 0; + + if(*csocket == NULL) { + rc = sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, csocket); + if (rc < 0) { + cERROR(1, ("Error %d creating socket",rc)); + *csocket = NULL; + return rc; + } else { + /* BB other socket options to set KEEPALIVE, NODELAY? */ + cFYI(1,("Socket created")); + (*csocket)->sk->sk_allocation = GFP_NOFS; + } + } + + psin_server->sin_family = AF_INET; + if(psin_server->sin_port) { /* user overrode default port */ + rc = (*csocket)->ops->connect(*csocket, + (struct sockaddr *) psin_server, + sizeof (struct sockaddr_in),0); + if (rc >= 0) + connected = 1; + } + + if(!connected) { + /* save original port so we can retry user specified port + later if fall back ports fail this time */ + orig_port = psin_server->sin_port; + + /* do not retry on the same port we just failed on */ + if(psin_server->sin_port != htons(CIFS_PORT)) { + psin_server->sin_port = htons(CIFS_PORT); + + rc = (*csocket)->ops->connect(*csocket, + (struct sockaddr *) psin_server, + sizeof (struct sockaddr_in),0); + if (rc >= 0) + connected = 1; + } + } + if (!connected) { + psin_server->sin_port = htons(RFC1001_PORT); + rc = (*csocket)->ops->connect(*csocket, (struct sockaddr *) + psin_server, sizeof (struct sockaddr_in),0); + if (rc >= 0) + connected = 1; + } + + /* give up here - unless we want to retry on different + protocol families some day */ + if (!connected) { + if(orig_port) + psin_server->sin_port = orig_port; + cFYI(1,("Error %d connecting to server via ipv4",rc)); + sock_release(*csocket); + *csocket = NULL; + return rc; + } + /* Eventually check for other socket options to change from + the default. sock_setsockopt not used because it expects + user space buffer */ + (*csocket)->sk->sk_rcvtimeo = 7 * HZ; + + /* send RFC1001 sessinit */ + + if(psin_server->sin_port == htons(RFC1001_PORT)) { + /* some servers require RFC1001 sessinit before sending + negprot - BB check reconnection in case where second + sessinit is sent but no second negprot */ + struct rfc1002_session_packet * ses_init_buf; + struct smb_hdr * smb_buf; + ses_init_buf = cifs_kcalloc(sizeof(struct rfc1002_session_packet), GFP_KERNEL); + if(ses_init_buf) { + ses_init_buf->trailer.session_req.called_len = 32; + rfc1002mangle(ses_init_buf->trailer.session_req.called_name, + DEFAULT_CIFS_CALLED_NAME,16); + ses_init_buf->trailer.session_req.calling_len = 32; + /* calling name ends in null (byte 16) from old smb + convention. */ + if(netbios_name && (netbios_name[0] !=0)) { + rfc1002mangle(ses_init_buf->trailer.session_req.calling_name, + netbios_name,16); + } else { + rfc1002mangle(ses_init_buf->trailer.session_req.calling_name, + "LINUX_CIFS_CLNT",16); + } + ses_init_buf->trailer.session_req.scope1 = 0; + ses_init_buf->trailer.session_req.scope2 = 0; + smb_buf = (struct smb_hdr *)ses_init_buf; + /* sizeof RFC1002_SESSION_REQUEST with no scope */ + smb_buf->smb_buf_length = 0x81000044; + rc = smb_send(*csocket, smb_buf, 0x44, + (struct sockaddr *)psin_server); + kfree(ses_init_buf); + } + /* else the negprot may still work without this + even though malloc failed */ + + } + + return rc; |