aboutsummaryrefslogtreecommitdiff
path: root/fs/cifs/connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/cifs/connect.c')
-rw-r--r--fs/cifs/connect.c3064
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;