/* * fs/cifs/connect.c * * Copyright (C) International Business Machines Corp., 2002,2011 * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" #include "fscache.h" #define CIFS_PORT 445 #define RFC1001_PORT 139 /* SMB echo "timeout" -- FIXME: tunable? */ #define SMB_ECHO_INTERVAL (60 * HZ) extern mempool_t *cifs_req_poolp; /* FIXME: should these be tunable? */ #define TLINK_ERROR_EXPIRE (1 * HZ) #define TLINK_IDLE_EXPIRE (600 * HZ) enum { /* Mount options that take no arguments */ Opt_user_xattr, Opt_nouser_xattr, Opt_forceuid, Opt_noforceuid, Opt_noblocksend, Opt_noautotune, Opt_hard, Opt_soft, Opt_perm, Opt_noperm, Opt_mapchars, Opt_nomapchars, Opt_sfu, Opt_nosfu, Opt_nodfs, Opt_posixpaths, Opt_noposixpaths, Opt_nounix, Opt_nocase, Opt_brl, Opt_nobrl, Opt_forcemandatorylock, Opt_setuids, Opt_nosetuids, Opt_dynperm, Opt_nodynperm, Opt_nohard, Opt_nosoft, Opt_nointr, Opt_intr, Opt_nostrictsync, Opt_strictsync, Opt_serverino, Opt_noserverino, Opt_rwpidforward, Opt_cifsacl, Opt_nocifsacl, Opt_acl, Opt_noacl, Opt_locallease, Opt_sign, Opt_seal, Opt_direct, Opt_strictcache, Opt_noac, Opt_fsc, Opt_mfsymlinks, Opt_multiuser, Opt_sloppy, /* Mount options which take numeric value */ Opt_backupuid, Opt_backupgid, Opt_uid, Opt_cruid, Opt_gid, Opt_file_mode, Opt_dirmode, Opt_port, Opt_rsize, Opt_wsize, Opt_actimeo, /* Mount options which take string value */ Opt_user, Opt_pass, Opt_ip, Opt_unc, Opt_domain, Opt_srcaddr, Opt_prefixpath, Opt_iocharset, Opt_sockopt, Opt_netbiosname, Opt_servern, Opt_ver, Opt_vers, Opt_sec, Opt_cache, /* Mount options to be ignored */ Opt_ignore, /* Options which could be blank */ Opt_blank_pass, Opt_blank_user, Opt_blank_ip, Opt_err }; static const match_table_t cifs_mount_option_tokens = { { Opt_user_xattr, "user_xattr" }, { Opt_nouser_xattr, "nouser_xattr" }, { Opt_forceuid, "forceuid" }, { Opt_noforceuid, "noforceuid" }, { Opt_noblocksend, "noblocksend" }, { Opt_noautotune, "noautotune" }, { Opt_hard, "hard" }, { Opt_soft, "soft" }, { Opt_perm, "perm" }, { Opt_noperm, "noperm" }, { Opt_mapchars, "mapchars" }, { Opt_nomapchars, "nomapchars" }, { Opt_sfu, "sfu" }, { Opt_nosfu, "nosfu" }, { Opt_nodfs, "nodfs" }, { Opt_posixpaths, "posixpaths" }, { Opt_noposixpaths, "noposixpaths" }, { Opt_nounix, "nounix" }, { Opt_nounix, "nolinux" }, { Opt_nocase, "nocase" }, { Opt_nocase, "ignorecase" }, { Opt_brl, "brl" }, { Opt_nobrl, "nobrl" }, { Opt_nobrl, "nolock" }, { Opt_forcemandatorylock, "forcemandatorylock" }, { Opt_forcemandatorylock, "forcemand" }, { Opt_setuids, "setuids" }, { Opt_nosetuids, "nosetuids" }, { Opt_dynperm, "dynperm" }, { Opt_nodynperm, "nodynperm" }, { Opt_nohard, "nohard" }, { Opt_nosoft, "nosoft" }, { Opt_nointr, "nointr" }, { Opt_intr, "intr" }, { Opt_nostrictsync, "nostrictsync" }, { Opt_strictsync, "strictsync" }, { Opt_serverino, "serverino" }, { Opt_noserverino, "noserverino" }, { Opt_rwpidforward, "rwpidforward" }, { Opt_cifsacl, "cifsacl" }, { Opt_nocifsacl, "nocifsacl" }, { Opt_acl, "acl" }, { Opt_noacl, "noacl" }, { Opt_locallease, "locallease" }, { Opt_sign, "sign" }, { Opt_seal, "seal" }, { Opt_direct, "direct" }, { Opt_direct, "directio" }, { Opt_direct, "forcedirectio" }, { Opt_strictcache, "strictcache" }, { Opt_noac, "noac" }, { Opt_fsc, "fsc" }, { Opt_mfsymlinks, "mfsymlinks" }, { Opt_multiuser, "multiuser" }, { Opt_sloppy, "sloppy" }, { Opt_backupuid, "backupuid=%s" }, { Opt_backupgid, "backupgid=%s" }, { Opt_uid, "uid=%s" }, { Opt_cruid, "cruid=%s" }, { Opt_gid, "gid=%s" }, { Opt_file_mode, "file_mode=%s" }, { Opt_dirmode, "dirmode=%s" }, { Opt_dirmode, "dir_mode=%s" }, { Opt_port, "port=%s" }, { Opt_rsize, "rsize=%s" }, { Opt_wsize, "wsize=%s" }, { Opt_actimeo, "actimeo=%s" }, { Opt_blank_user, "user=" }, { Opt_blank_user, "username=" }, { Opt_user, "user=%s" }, { Opt_user, "username=%s" }, { Opt_blank_pass, "pass=" }, { Opt_pass, "pass=%s" }, { Opt_pass, "password=%s" }, { Opt_blank_ip, "ip=" }, { Opt_blank_ip, "addr=" }, { Opt_ip, "ip=%s" }, { Opt_ip, "addr=%s" }, { Opt_unc, "unc=%s" }, { Opt_unc, "target=%s" }, { Opt_unc, "path=%s" }, { Opt_domain, "dom=%s" }, { Opt_domain, "domain=%s" }, { Opt_domain, "workgroup=%s" }, { Opt_srcaddr, "srcaddr=%s" }, { Opt_prefixpath, "prefixpath=%s" }, { Opt_iocharset, "iocharset=%s" }, { Opt_sockopt, "sockopt=%s" }, { Opt_netbiosname, "netbiosname=%s" }, { Opt_servern, "servern=%s" }, { Opt_ver, "ver=%s" }, { Opt_vers, "vers=%s" }, { Opt_sec, "sec=%s" }, { Opt_cache, "cache=%s" }, { Opt_ignore, "cred" }, { Opt_ignore, "credentials" }, { Opt_ignore, "cred=%s" }, { Opt_ignore, "credentials=%s" }, { Opt_ignore, "guest" }, { Opt_ignore, "rw" }, { Opt_ignore, "ro" }, { Opt_ignore, "suid" }, { Opt_ignore, "nosuid" }, { Opt_ignore, "exec" }, { Opt_ignore, "noexec" }, { Opt_ignore, "nodev" }, { Opt_ignore, "noauto" }, { Opt_ignore, "dev" }, { Opt_ignore, "mand" }, { Opt_ignore, "nomand" }, { Opt_ignore, "_netdev" }, { Opt_err, NULL } }; enum { Opt_sec_krb5, Opt_sec_krb5i, Opt_sec_krb5p, Opt_sec_ntlmsspi, Opt_sec_ntlmssp, Opt_ntlm, Opt_sec_ntlmi, Opt_sec_ntlmv2i, Opt_sec_nontlm, Opt_sec_lanman, Opt_sec_none, Opt_sec_err }; static const match_table_t cifs_secflavor_tokens = { { Opt_sec_krb5, "krb5" }, { Opt_sec_krb5i, "krb5i" }, { Opt_sec_krb5p, "krb5p" }, { Opt_sec_ntlmsspi, "ntlmsspi" }, { Opt_sec_ntlmssp, "ntlmssp" }, { Opt_ntlm, "ntlm" }, { Opt_sec_ntlmi, "ntlmi" }, { Opt_sec_ntlmv2i, "ntlmv2i" }, { Opt_sec_nontlm, "nontlm" }, { Opt_sec_lanman, "lanman" }, { Opt_sec_none, "none" }, { Opt_sec_err, NULL } }; /* cache flavors */ enum { Opt_cache_loose, Opt_cache_strict, Opt_cache_none, Opt_cache_err }; static const match_table_t cifs_cacheflavor_tokens = { { Opt_cache_loose, "loose" }, { Opt_cache_strict, "strict" }, { Opt_cache_none, "none" }, { Opt_cache_err, NULL } }; static const match_table_t cifs_smb_version_tokens = { { Smb_1, SMB1_VERSION_STRING }, { Smb_21, SMB21_VERSION_STRING }, }; static int ip_connect(struct TCP_Server_Info *server); static int generic_ip_connect(struct TCP_Server_Info *server); static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink); static void cifs_prune_tlinks(struct work_struct *work); static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, const char *devname); /* * 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) */ static int cifs_reconnect(struct TCP_Server_Info *server) { int rc = 0; struct list_head *tmp, *tmp2; struct cifs_ses *ses; struct cifs_tcon *tcon; struct mid_q_entry *mid_entry; struct list_head retry_list; 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 */ cFYI(1, "%s: marking sessions and tcons for reconnect", __func__); spin_lock(&cifs_tcp_ses_lock); list_for_each(tmp, &server->smb_ses_list) { ses = list_entry(tmp, struct cifs_ses, smb_ses_list); ses->need_reconnect = true; ses->ipc_tid = 0; list_for_each(tmp2, &ses->tcon_list) { tcon = list_entry(tmp2, struct cifs_tcon, tcon_list); tcon->need_reconnect = true; } } spin_unlock(&cifs_tcp_ses_lock); /* do not want to be sending data on a socket we are freeing */ cFYI(1, "%s: tearing down socket", __func__); mutex_lock(&server->srv_mutex); if (server->ssocket) { cFYI(1, "State: 0x%x Flags: 0x%lx", server->ssocket->state, server->ssocket->flags); kernel_sock_shutdown(server->ssocket, SHUT_WR); cFYI(1, "Post shutdown state: 0x%x Flags: 0x%lx", server->ssocket->state, server->ssocket->flags); sock_release(server->ssocket); server->ssocket = NULL; } server->sequence_number = 0; server->session_estab = false; kfree(server->session_key.response); server->session_key.response = NULL; server->session_key.len = 0; server->lstrp = jiffies; mutex_unlock(&server->srv_mutex); /* mark submitted MIDs for retry and issue callback */ INIT_LIST_HEAD(&retry_list); cFYI(1, "%s: moving mids to private list", __func__); spin_lock(&GlobalMid_Lock); list_for_each_safe(tmp, tmp2, &server->pending_mid_q) { mid_entry = list_entry(tmp, struct mid_q_entry, qhead); if (mid_entry->mid_state == MID_REQUEST_SUBMITTED) mid_entry->mid_state = MID_RETRY_NEEDED; list_move(&mid_entry->qhead, &retry_list); } spin_unlock(&GlobalMid_Lock); cFYI(1, "%s: issuing mid callbacks", __func__); list_for_each_safe(tmp, tmp2, &retry_list) { mid_entry = list_entry(tmp, struct mid_q_entry, qhead); list_del_init(&mid_entry->qhead); mid_entry->callback(mid_entry); } do { try_to_freeze(); /* we should try only the port we connected to before */ rc = generic_ip_connect(server); if (rc) { cFYI(1, "reconnect error %d", rc); msleep(3000); } else { atomic_inc(&tcpSesReconnectCount); spin_lock(&GlobalMid_Lock); if (server->tcpStatus != CifsExiting) server->tcpStatus = CifsNeedNegotiate; spin_unlock(&GlobalMid_Lock); } } while (server->tcpStatus == CifsNeedReconnect); return rc; } /* return codes: 0 not a transact2, or all data present >0 transact2 with that much data missing -EINVAL = invalid transact2 */ static int check2ndT2(char *buf) { struct smb_hdr *pSMB = (struct smb_hdr *)buf; struct smb_t2_rsp *pSMBt; int remaining; __u16 total_data_size, data_in_this_rsp; if (pSMB->Command != SMB_COM_TRANSACTION2) return 0; /* check for plausible wct, bcc and t2 data and parm sizes */ /* check for parm and data offset going beyond end of smb */ if (pSMB->WordCount != 10) { /* coalesce_t2 depends on this */ cFYI(1, "invalid transact2 word count"); return -EINVAL; } pSMBt = (struct smb_t2_rsp *)pSMB; total_data_size = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount); data_in_this_rsp = get_unaligned_le16(&pSMBt->t2_rsp.DataCount); if (total_data_size == data_in_this_rsp) return 0; else if (total_data_size < data_in_this_rsp) { cFYI(1, "total data %d smaller than data in frame %d", total_data_size, data_in_this_rsp); return -EINVAL; } remaining = total_data_size - data_in_this_rsp; cFYI(1, "missing %d bytes from transact2, check next response", remaining); if (total_data_size > CIFSMaxBufSize) { cERROR(1, "TotalDataSize %d is over maximum buffer %d", total_data_size, CIFSMaxBufSize); return -EINVAL; } return remaining; } static int coalesce_t2(char *second_buf, struct smb_hdr *target_hdr) { struct smb_t2_rsp *pSMBs = (struct smb_t2_rsp *)second_buf; struct smb_t2_rsp *pSMBt = (struct smb_t2_rsp *)target_hdr; char *data_area_of_tgt; char *data_area_of_src; int remaining; unsigned int byte_count, total_in_tgt; __u16 tgt_total_cnt, src_total_cnt, total_in_src; src_total_cnt = get_unaligned_le16(&pSMBs->t2_rsp.TotalDataCount); tgt_total_cnt = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount); if (tgt_total_cnt != src_total_cnt) cFYI(1, "total data count of primary and secondary t2 differ " "source=%hu target=%hu", src_total_cnt, tgt_total_cnt); total_in_tgt = get_unaligned_le16(&pSMBt->t2_rsp.DataCount); remaining = tgt_total_cnt - total_in_tgt; if (remaining < 0) { cFYI(1, "Server sent too much data. tgt_total_cnt=%hu " "total_in_tgt=%hu", tgt_total_cnt, total_in_tgt); return -EPROTO; } if (remaining == 0) { /* nothing to do, ignore */ cFYI(1, "no more data remains"); return 0; } total_in_src = get_unaligned_le16(&pSMBs->t2_rsp.DataCount); if (remaining < total_in_src) cFYI(1, "transact2 2nd response contains too much data"); /* find end of first SMB data area */ data_area_of_tgt = (char *)&pSMBt->hdr.Protocol + get_unaligned_le16(&pSMBt->t2_rsp.DataOffset); /* validate target area */ data_area_of_src = (char *)&pSMBs->hdr.Protocol + get_unaligned_le16(&pSMBs->t2_rsp.DataOffset); data_area_of_tgt += total_in_tgt; total_in_tgt += total_in_src; /* is the result too big for the field? */ if (total_in_tgt > USHRT_MAX) { cFYI(1, "coalesced DataCount too large (%u)", total_in_tgt); return -EPROTO; } put_unaligned_le16(total_in_tgt, &pSMBt->t2_rsp.DataCount); /* fix up the BCC */ byte_count = get_bcc(target_hdr); byte_count += total_in_src; /* is the result too big for the field? */ if (byte_count > USHRT_MAX) { cFYI(1, "coalesced BCC too large (%u)", byte_count); return -EPROTO; } put_bcc(byte_count, target_hdr); byte_count = be32_to_cpu(target_hdr->smb_buf_length); byte_count += total_in_src; /* don't allow buffer to overflow */ if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { cFYI(1, "coalesced BCC exceeds buffer size (%u)", byte_count); return -ENOBUFS; } target_hdr->smb_buf_length = cpu_to_be32(byte_count); /* copy second buffer into end of first buffer */ memcpy(data_area_of_tgt, data_area_of_src, total_in_src); if (remaining != total_in_src) { /* more responses to go */ cFYI(1, "waiting for more secondary responses"); return 1; } /* we are done */ cFYI(1, "found the last secondary response"); return 0; } static void cifs_echo_request(struct work_struct *work) { int rc; struct TCP_Server_Info *server = container_of(work, struct TCP_Server_Info, echo.work); /* * We cannot send an echo until the NEGOTIATE_PROTOCOL request is * done, which is indicated by maxBuf != 0. Also, no need to ping if * we got a response recently */ if (server->maxBuf == 0 || time_before(jiffies, server->lstrp + SMB_ECHO_INTERVAL - HZ)) goto requeue_echo; rc = CIFSSMBEcho(server); if (rc) cFYI(1, "Unable to send echo request to server: %s", server->hostname); requeue_echo: queue_delayed_work(cifsiod_wq, &server->echo, SMB_ECHO_INTERVAL); } static bool allocate_buffers(struct TCP_Server_Info *server) { if (!server->bigbuf) { server->bigbuf = (char *)cifs_buf_get(); if (!server->bigbuf) { cERROR(1, "No memory for large SMB response"); msleep(3000); /* retry will check if exiting */ return false; } } else if (server->large_buf) { /* we are reusing a dirty large buf, clear its start */ memset(server->bigbuf, 0, HEADER_SIZE(server)); } if (!server->smallbuf) { server->smallbuf = (char *)cifs_small_buf_get(); if (!server->smallbuf) { cERROR(1, "No memory for SMB response"); msleep(1000); /* retry will check if exiting */ return false; } /* beginning of smb buffer is cleared in our buf_get */ } else { /* if existing small buf clear beginning */ memset(server->smallbuf, 0, HEADER_SIZE(server)); } return true; } static bool server_unresponsive(struct TCP_Server_Info *server) { /* * We need to wait 2 echo intervals to make sure we handle such * situations right: * 1s client sends a normal SMB request * 2s client gets a response * 30s echo workqueue job pops, and decides we got a response recently * and don't need to send another * ... * 65s kernel_recvmsg times out, and we see that we haven't gotten * a response in >60s. */ if (server->tcpStatus == CifsGood && time_after(jiffies, server->lstrp + 2 * SMB_ECHO_INTERVAL)) { cERROR(1, "Server %s has not responded in %d seconds. " "Reconnecting...", server->hostname, (2 * SMB_ECHO_INTERVAL) / HZ); cifs_reconnect(server); wake_up(&server->response_q); return true; } return false; } /* * kvec_array_init - clone a kvec array, and advance into it * @new: pointer to memory for cloned array * @iov: pointer to original array * @nr_segs: number of members in original array * @bytes: number of bytes to advance into the cloned array * * This function will copy the array provided in iov to a section of memory * and advance the specified number of bytes into the new array. It returns * the number of segments in the new array. "new" must be at least as big as * the original iov array. */ static unsigned int kvec_array_init(struct kvec *new, struct kvec *iov, unsigned int nr_segs, size_t bytes) { size_t base = 0; while (bytes || !iov->iov_len) { int copy = min(bytes, iov->iov_len); bytes -= copy; base += copy; if (iov->iov_len == base) { iov++; nr_segs--; base = 0; } } memcpy(new, iov, sizeof(*iov) * nr_segs); new->iov_base += base; new->iov_len -= base; return nr_segs; } static struct kvec * get_server_iovec(struct TCP_Server_Info *server, unsigned int nr_segs) { struct kvec *new_iov; if (server->iov && nr_segs <= server->nr_iov) return server->iov; /* not big enough -- allocate a new one and release the old */ new_iov = kmalloc(sizeof(*new_iov) * nr_segs, GFP_NOFS); if (new_iov) { kfree(server->iov); server->iov = new_iov; server->nr_iov = nr_segs; } return new_iov; } int cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, unsigned int nr_segs, unsigned int to_read) { int length = 0; int total_read; unsigned int segs; struct msghdr smb_msg; struct kvec *iov; iov = get_server_iovec(server, nr_segs); if (!iov) return -ENOMEM; smb_msg.msg_control = NULL; smb_msg.msg_controllen = 0; for (total_read = 0; to_read; total_read += length, to_read -= length) { try_to_freeze(); if (server_unresponsive(server)) { total_read = -EAGAIN; break; } segs = kvec_array_init(iov, iov_orig, nr_segs, total_read); length = kernel_recvmsg(server->ssocket, &smb_msg, iov, segs, to_read, 0); if (server->tcpStatus == CifsExiting) { total_read = -ESHUTDOWN; break; } else if (server->tcpStatus == CifsNeedReconnect) { cifs_reconnect(server); total_read = -EAGAIN; break; } else if (length == -ERESTARTSYS || length == -EAGAIN || length == -EINTR) { /* * Minimum sleep to prevent looping, allowing socket * to clear and app threads to set tcpStatus * CifsNeedReconnect if server hung. */ usleep_range(1000, 2000); length = 0; continue; } else if (length <= 0) { cFYI(1, "Received no data or error: expecting %d " "got %d", to_read, length); cifs_reconnect(server); total_read = -EAGAIN; break; } } return total_read; } int cifs_read_from_socket(struct TCP_Server_Info *server, char *buf, unsigned int to_read) { struct kvec iov; iov.iov_base = buf; iov.iov_len = to_read; return cifs_readv_from_socket(server, &iov, 1, to_read); } static bool is_smb_response(struct TCP_Server_Info *server, unsigned char type) { /* * The first byte big endian of the length field, * is actually not part of the length but the type * with the most common, zero, as regular data. */ switch (type) { case RFC1002_SESSION_MESSAGE: /* Regular SMB response */ return true; case RFC1002_SESSION_KEEP_ALIVE: cFYI(1, "RFC 1002 session keep alive"); break; case RFC1002_POSITIVE_SESSION_RESPONSE: cFYI(1, "RFC 1002 positive session response"); break; case RFC1002_NEGATIVE_SESSION_RESPONSE: /* * We get this from Windows 98 instead of an error on * SMB negprot response. */ cFYI(1, "RFC 1002 negative session response"); /* give server a second to clean up */ msleep(1000); /* * 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). */ cifs_set_port((struct sockaddr *)&server->dstaddr, CIFS_PORT); cifs_reconnect(server); wake_up(&server->response_q); break; default: cERROR(1, "RFC 1002 unknown response type 0x%x", type); cifs_reconnect(server); } return false; } void dequeue_mid(struct mid_q_entry *mid, bool malformed) { #ifdef CONFIG_CIFS_STATS2 mid->when_received = jiffies; #endif spin_lock(&GlobalMid_Lock); if (!malformed) mid->mid_state = MID_RESPONSE_RECEIVED; else mid->mid_state = MID_RESPONSE_MALFORMED; list_del_init(&mid->qhead); spin_unlock(&GlobalMid_Lock); } static void handle_mid(struct mid_q_entry *mid, struct TCP_Server_Info *server, char *buf, int malformed) { if (malformed == 0 && check2ndT2(buf) > 0) { mid->multiRsp = true; if (mid->resp_buf) { /* merge response - fix up 1st*/ malformed = coalesce_t2(buf, mid->resp_buf); if (malformed > 0) return; /* All parts received or packet is malformed. */ mid->multiEnd = true; return dequeue_mid(mid, malformed); } if (!server->large_buf) { /*FIXME: switch to already allocated largebuf?*/ cERROR(1, "1st trans2 resp needs bigbuf"); } else { /* Have first buffer */ mid->resp_buf = buf; mid->large_buf = true; server->bigbuf = NULL; } return; } mid->resp_buf = buf; mid->large_buf = server->large_buf; /* Was previous buf put in mpx struct for multi-rsp? */ if (!mid->multiRsp) { /* smb buffer will be freed by user thread */ if (server->large_buf) server->bigbuf = NULL; else server->smallbuf = NULL; } dequeue_mid(mid, malformed); } static void clean_demultiplex_info(struct TCP_Server_Info *server) { int length; /* take it off the list, if it's not already */ spin_lock(&cifs_tcp_ses_lock); list_del_init(&server->tcp_ses_list); spin_unlock(&cifs_tcp_ses_lock); spin_lock(&GlobalMid_Lock); server->tcpStatus = CifsExiting; spin_unlock(&GlobalMid_Lock); wake_up_all(&server->response_q); /* check if we have blocked requests that need to free */ spin_lock(&server->req_lock); if (server->credits <= 0) server->credits = 1; spin_unlock(&server->req_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 */ msleep(125); if (server->ssocket) { sock_release(server->ssocket); server->ssocket = NULL; } if (!list_empty(&server->pending_mid_q)) { struct list_head dispose_list; struct mid_q_entry *mid_entry; struct list_head *tmp, *tmp2; INIT_LIST_HEAD(&dispose_list); spin_lock(&GlobalMid_Lock); list_for_each_safe(tmp, tmp2, &server->pending_mid_q) { mid_entry = list_entry(tmp, struct mid_q_entry, qhead); cFYI(1, "Clearing mid 0x%llx", mid_entry->mid); mid_entry->mid_state = MID_SHUTDOWN; list_move(&mid_entry->qhead, &dispose_list); } spin_unlock(&GlobalMid_Lock); /* now walk dispose list and issue callbacks */ list_for_each_safe(tmp, tmp2, &dispose_list) { mid_entry = list_entry(tmp, struct mid_q_entry, qhead); cFYI(1, "Callback mid 0x%llx", mid_entry->mid); list_del_init(&mid_entry->qhead); mid_entry->callback(mid_entry); } /* 1/8th of sec is more than enough time for them to exit */ msleep(125); } 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. * * Due to delays on oplock break requests, we need to wait at * least 45 seconds before giving up on a request getting a * response and going ahead and killing cifsd. */ cFYI(1, "Wait for exit from demultiplex thread"); msleep(46000); /* * If threads still have not exited they are probably never * coming home not much else we can do but free the memory. */ } kfree(server->hostname); kfree(server->iov); kfree(server); length = atomic_dec_return(&tcpSesAllocCount); if (length > 0) mempool_resize(cifs_req_poolp, length + cifs_min_rcv, GFP_KERNEL); } static int standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid) { int length; char *buf = server->smallbuf; unsigned int pdu_length = get_rfc1002_length(buf); /* make sure this will fit in a large buffer */ if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server) - 4) { cERROR(1, "SMB response too long (%u bytes)", pdu_length); cifs_reconnect(server); wake_up(&server->response_q); return -EAGAIN; } /* switch to large buffer if too big for a small one */ if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { server->large_buf = true; memcpy(server->bigbuf, buf, server->total_read); buf = server->bigbuf; } /* now read the rest */ length = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1, pdu_length - HEADER_SIZE(server) + 1 + 4); if (length < 0) return length; server->total_read += length; dump_smb(buf, server->total_read); /* * We know that we received enough to get to the MID as we * checked the pdu_length earlier. Now check to see * if the rest of the header is OK. We borrow the length * var for the rest of the loop to avoid a new stack var. * * 48 bytes is enough to display the header and a little bit * into the payload for debugging purposes. */ length = server->ops->check_message(buf, server->total_read); if (length != 0) cifs_dump_mem("Bad SMB: ", buf, min_t(unsigned int, server->total_read, 48)); if (!mid) return length; handle_mid(mid, server, buf, length); return 0; } static int cifs_demultiplex_thread(void *p) { int length; struct TCP_Server_Info *server = p; unsigned int pdu_length; char *buf = NULL; struct task_struct *task_to_wake = NULL; struct mid_q_entry *mid_entry; current->flags |= PF_MEMALLOC; cFYI(1, "Demultiplex PID: %d", task_pid_nr(current)); length = atomic_inc_return(&tcpSesAllocCount); if (length > 1) mempool_resize(cifs_req_poolp, length + cifs_min_rcv, GFP_KERNEL); set_freezable(); while (server->tcpStatus != CifsExiting) { if (try_to_freeze()) continue; if (!allocate_buffers(server)) continue; server->large_buf = false; buf = server->smallbuf; pdu_length = 4; /* enough to get RFC1001 header */ length = cifs_read_from_socket(server, buf, pdu_length); if (length < 0) continue; server->total_read = length; /* * The right amount was read from socket - 4 bytes, * so we can now interpret the length field. */ pdu_length = get_rfc1002_length(buf); cFYI(1, "RFC1002 header 0x%x", pdu_length); if (!is_smb_response(server, buf[0])) continue; /* make sure we have enough to get to the MID */ if (pdu_length < HEADER_SIZE(server) - 1 - 4) { cERROR(1, "SMB response too short (%u bytes)", pdu_length); cifs_reconnect(server); wake_up(&server->response_q); continue; } /* read down to the MID */ length = cifs_read_from_socket(server, buf + 4, HEADER_SIZE(server) - 1 - 4); if (length < 0) continue; server->total_read += length; mid_entry = server->ops->find_mid(server, buf); if (!mid_entry || !mid_entry->receive) length = standard_receive3(server, mid_entry); else length = mid_entry->receive(server, mid_entry); if (length < 0) continue; if (server->large_buf) buf = server->bigbuf; server->lstrp = jiffies; if (mid_entry != NULL) { if (!mid_entry->multiRsp || mid_entry->multiEnd) mid_entry->callback(mid_entry); } else if (!server->ops->is_oplock_break || !server->ops->is_oplock_break(buf, server)) { cERROR(1, "No task to wake, unknown frame received! " "NumMids %d", atomic_read(&midCount)); cifs_dump_mem("Received Data is: ", buf, HEADER_SIZE(server)); #ifdef CONFIG_CIFS_DEBUG2 if (server->ops->dump_detail) server->ops->dump_detail(buf); cifs_dump_mids(server); #endif /* CIFS_DEBUG2 */ } } /* end while !EXITING */ /* buffer usually freed in free_mid - need to free it here on exit */ cifs_buf_release(server->bigbuf); if (server->smallbuf) /* no sense logging a debug message if NULL */ cifs_small_buf_release(server->smallbuf); task_to_wake = xchg(&server->tsk, NULL); clean_demultiplex_info(server); /* if server->tsk was NULL then wait for a signal before exiting */ if (!task_to_wake) { set_current_state(TASK_INTERRUPTIBLE); while (!signal_pending(current)) { schedule(); set_current_state(TASK_INTERRUPTIBLE); } set_current_state(TASK_RUNNING); } module_put_and_exit(0); } /* extract the host portion of the UNC string */ static char * extract_hostname(const char *unc) { const char *src; char *dst, *delim; unsigned int len; /* skip double chars at beginning of string */ /* BB: check validity of these bytes? */ src = unc + 2; /* delimiter between hostname and sharename is always '\\' now */ delim = strchr(src, '\\'); if (!delim) return ERR_PTR(-EINVAL); len = delim - src; dst = kmalloc((len + 1), GFP_KERNEL); if (dst == NULL) return ERR_PTR(-ENOMEM); memcpy(dst, src, len); dst[len] = '\0'; return dst; } static int get_option_ul(substring_t args[], unsigned long *option) { int rc; char *string; string = match_strdup(args); if (string == NULL) return -ENOMEM; rc = kstrtoul(string, 0, option); kfree(string); return rc; } static int cifs_parse_security_flavors(char *value, struct smb_vol *vol) { substring_t args[MAX_OPT_ARGS]; switch (match_token(value, cifs_secflavor_tokens, args)) { case Opt_sec_krb5: vol->secFlg |= CIFSSEC_MAY_KRB5; break; case Opt_sec_krb5i: vol->secFlg |= CIFSSEC_MAY_KRB5 | CIFSSEC_MUST_SIGN; break; case Opt_sec_krb5p: /* vol->secFlg |= CIFSSEC_MUST_SEAL | CIFSSEC_MAY_KRB5; */ cERROR(1, "Krb5 cifs privacy not supported"); break; case Opt_sec_ntlmssp: vol->secFlg |= CIFSSEC_MAY_NTLMSSP; break; case Opt_sec_ntlmsspi: vol->secFlg |= CIFSSEC_MAY_NTLMSSP | CIFSSEC_MUST_SIGN; break; case Opt_ntlm: /* ntlm is default so can be turned off too */ vol->secFlg |= CIFSSEC_MAY_NTLM; break; case Opt_sec_ntlmi: vol->secFlg |= CIFSSEC_MAY_NTLM | CIFSSEC_MUST_SIGN; break; case Opt_sec_nontlm: vol->secFlg |= CIFSSEC_MAY_NTLMV2; break; case Opt_sec_ntlmv2i: vol->secFlg |= CIFSSEC_MAY_NTLMV2 | CIFSSEC_MUST_SIGN; break; #ifdef CONFIG_CIFS_WEAK_PW_HASH case Opt_sec_lanman: vol->secFlg |= CIFSSEC_MAY_LANMAN; break; #endif case Opt_sec_none: vol->nullauth = 1; break; default: cERROR(1, "bad security option: %s", value); return 1; } return 0; } static int cifs_parse_cache_flavor(char *value, struct smb_vol *vol) { substring_t args[MAX_OPT_ARGS]; switch (match_token(value, cifs_cacheflavor_tokens, args)) { case Opt_cache_loose: vol->direct_io = false; vol->strict_io = false; break; case Opt_cache_strict: vol->direct_io = false; vol->strict_io = true; break; case Opt_cache_none: vol->direct_io = true; vol->strict_io = false; break; default: cERROR(1, "bad cache= option: %s", value); return 1; } return 0; } static int cifs_parse_smb_version(char *value, struct smb_vol *vol) { substring_t args[MAX_OPT_ARGS]; switch (match_token(value, cifs_smb_version_tokens, args)) { case Smb_1: vol->ops = &smb1_operations; vol->vals = &smb1_values; break; #ifdef CONFIG_CIFS_SMB2 case Smb_21: vol->ops = &smb21_operations; vol->vals = &smb21_values; break; #endif default: cERROR(1, "Unknown vers= option specified: %s", value); return 1; } return 0; } static int cifs_parse_mount_options(const char *mountdata, const char *devname, struct smb_vol *vol) { char *data, *end; char *mountdata_copy = NULL, *options; unsigned int temp_len, i, j; char separator[2]; short int override_uid = -1; short int override_gid = -1; bool uid_specified = false; bool gid_specified = false; bool sloppy = false; char *invalid = NULL; char *nodename = utsname()->nodename; char *string = NULL; char *tmp_end, *value; char delim; bool cache_specified = false; static bool cache_warned = false; separator[0] = ','; separator[1] = 0; delim = separator[0]; /* * does not have to be perfect mapping since field is * informational, only used for servers that do not support * port 445 and it can be overridden at mount time */ memset(vol->source_rfc1001_name, 0x20, RFC1001_NAME_LEN); for (i = 0; i < strnlen(nodename, RFC1001_NAME_LEN); i++) vol->source_rfc1001_name[i] = toupper(nodename[i]); vol->source_rfc1001_name[RFC1001_NAME_LEN] = 0; /* null target name indicates to use *SMBSERVR default called name if we end up sending RFC1001 session initialize */ vol->target_rfc1001_name[0] = 0; vol->cred_uid = current_uid(); vol->linux_uid = current_uid(); vol->linux_gid = current_gid(); /* default to only allowing write access to owner of the mount */ vol->dir_mode = vol->file_mode = S_IRUGO | S_IXUGO | S_IWUSR; /* vol->retry default is 0 (i.e. "soft" limited retry not hard retry) */ /* default is always to request posix paths. */ vol->posix_paths = 1; /* default to using server inode numbers where available */ vol->server_ino = 1; vol->actimeo = CIFS_DEF_ACTIMEO; /* FIXME: add autonegotiation -- for now, SMB1 is default */ vol->ops = &smb1_operations; vol->vals = &smb1_values; if (!mountdata) goto cifs_parse_mount_err; mountdata_copy = kstrndup(mountdata, PAGE_SIZE, GFP_KERNEL); if (!mountdata_copy) goto cifs_parse_mount_err; options = mountdata_copy; end = options + strlen(options); if (strncmp(options, "sep=", 4) == 0) { if (options[4] != 0) { separator[0] = options[4]; options += 5; } else { cFYI(1, "Null separator not allowed"); } } vol->backupuid_specified = false; /* no backup intent for a user */ vol->backupgid_specified = false; /* no backup intent for a group */ while ((data = strsep(&options, separator)) != NULL) { substring_t args[MAX_OPT_ARGS]; unsigned long option; int token; if (!*data) continue; token = match_token(data, cifs_mount_option_tokens, args); switch (token) { /* Ingnore the following */ case Opt_ignore: break; /* Boolean values */ case Opt_user_xattr: vol->no_xattr = 0; break; case Opt_nouser_xattr: vol->no_xattr = 1; break; case Opt_forceuid: override_uid = 1; break; case Opt_noforceuid: override_uid = 0; break; case Opt_noblocksend: vol->noblocksnd = 1; break; case Opt_noautotune: vol->noautotune = 1; break; case Opt_hard: vol->retry = 1; break; case Opt_soft: vol->retry = 0; break; case Opt_perm: vol->noperm = 0; break; case Opt_noperm: vol->noperm = 1; break; case Opt_mapchars: vol->remap = 1; break; case Opt_nomapchars: vol->remap = 0; break; case Opt_sfu: vol->sfu_emul = 1; break; case Opt_nosfu: vol->sfu_emul = 0; break; case Opt_nodfs: vol->nodfs = 1; break; case Opt_posixpaths: vol->posix_paths = 1; break; case Opt_noposixpaths: vol->posix_paths = 0; break; case Opt_nounix: vol->no_linux_ext = 1; break; case Opt_nocase: vol->nocase = 1; break; case Opt_brl: vol->nobrl = 0; break; case Opt_nobrl: vol->nobrl = 1; /* * turn off mandatory locking in mode * if remote locking is turned off since the * local vfs will do advisory */ if (vol->file_mode == (S_IALLUGO & ~(S_ISUID | S_IXGRP))) vol->file_mode = S_IALLUGO; break; case Opt_forcemandatorylock: vol->mand_lock = 1; break; case Opt_setuids: vol->setuids = 1; break; case Opt_nosetuids: vol->setuids = 0; break; case Opt_dynperm: vol->dynperm = true; break; case Opt_nodynperm: vol->dynperm = false; break; case Opt_nohard: vol->retry = 0; break; case Opt_nosoft: vol->retry = 1; break; case Opt_nointr: vol->intr = 0; break; case Opt_intr: vol->intr = 1; break; case Opt_nostrictsync: vol->nostrictsync = 1; break; case Opt_strictsync: vol->nostrictsync = 0; break; case Opt_serverino: vol->server_ino = 1; break; case Opt_noserverino: vol->server_ino = 0; break; case Opt_rwpidforward: vol->rwpidforward = 1; break; case Opt_cifsacl: vol->cifs_acl = 1; break; case Opt_nocifsacl: vol->cifs_acl = 0; break; case Opt_acl: vol->no_psx_acl = 0; break; case Opt_noacl: vol->no_psx_acl = 1; break; case Opt_locallease: vol->local_lease = 1; break; case Opt_sign: vol->secFlg |= CIFSSEC_MUST_SIGN; break; case Opt_seal: /* we do not do the following in secFlags because seal * is a per tree connection (mount) not a per socket * or per-smb connection option in the protocol * vol->secFlg |= CIFSSEC_MUST_SEAL; */ vol->seal = 1; break; case Opt_direct: cache_specified = true; vol->direct_io = true; vol->strict_io = false; cERROR(1, "The \"directio\" option will be removed in " "3.7. Please switch to the \"cache=none\" " "option."); break; case Opt_strictcache: cache_specified = true; vol->direct_io = false; vol->strict_io = true; cERROR(1, "The \"strictcache\" option will be removed " "in 3.7. Please switch to the \"cache=strict\" " "option."); break; case Opt_noac: printk(KERN_WARNING "CIFS: Mount option noac not " "supported. Instead set " "/proc/fs/cifs/LookupCacheEnabled to 0\n"); break; case Opt_fsc: #ifndef CONFIG_CIFS_FSCACHE cERROR(1, "FS-Cache support needs CONFIG_CIFS_FSCACHE " "kernel config option set"); goto cifs_parse_mount_err; #endif vol->fsc = true; break; case Opt_mfsymlinks: vol->mfsymlinks = true; break; case Opt_multiuser: vol->multiuser = true; break; case Opt_sloppy: sloppy = true; break; /* Numeric Values */ case Opt_backupuid: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid backupuid value", __func__); goto cifs_parse_mount_err; } vol->backupuid = option; vol->backupuid_specified = true; break; case Opt_backupgid: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid backupgid value", __func__); goto cifs_parse_mount_err; } vol->backupgid = option; vol->backupgid_specified = true; break; case Opt_uid: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid uid value", __func__); goto cifs_parse_mount_err; } vol->linux_uid = option; uid_specified = true; break; case Opt_cruid: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid cruid value", __func__); goto cifs_parse_mount_err; } vol->cred_uid = option; break; case Opt_gid: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid gid value", __func__); goto cifs_parse_mount_err; } vol->linux_gid = option; gid_specified = true; break; case Opt_file_mode: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid file_mode value", __func__); goto cifs_parse_mount_err; } vol->file_mode = option; break; case Opt_dirmode: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid dir_mode value", __func__); goto cifs_parse_mount_err; } vol->dir_mode = option; break; case Opt_port: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid port value", __func__); goto cifs_parse_mount_err; } vol->port = option; break; case Opt_rsize: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid rsize value", __func__); goto cifs_parse_mount_err; } vol->rsize = option; break; case Opt_wsize: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid wsize value", __func__); goto cifs_parse_mount_err; } vol->wsize = option; break; case Opt_actimeo: if (get_option_ul(args, &option)) { cERROR(1, "%s: Invalid actimeo value", __func__); goto cifs_parse_mount_err; } vol->actimeo = HZ * option; if (vol->actimeo > CIFS_MAX_ACTIMEO) { cERROR(1, "CIFS: attribute cache" "timeout too large"); goto cifs_parse_mount_err; } break; /* String Arguments */ case Opt_blank_user: /* null user, ie. anonymous authentication */ vol->nullauth = 1; vol->username = NULL; break; case Opt_user: string = match_strdup(args); if (string == NULL) goto out_nomem; if (strnlen(string, MAX_USERNAME_SIZE) > MAX_USERNAME_SIZE) { printk(KERN_WARNING "CIFS: username too long\n"); goto cifs_parse_mount_err; } vol->username = kstrdup(string, GFP_KERNEL); if (!vol->username) { printk(KERN_WARNING "CIFS: no memory " "for username\n"); goto cifs_parse_mount_err; } break; case Opt_blank_pass: vol->password = NULL; break; case Opt_pass: /* passwords have to be handled differently * to allow the character used for deliminator * to be passed within them */ /* Obtain the value string */ value = strchr(data, '='); value++; /* Set tmp_end to end of the string */ tmp_end = (char *) value + strlen(value); /* Check if following character is the deliminator * If yes, we have encountered a double deliminator * reset the NULL character to the deliminator */ if (tmp_end < end && tmp_end[1] == delim) tmp_end[0] = delim; /* Keep iterating until we get to a single deliminator * OR the end */ while ((tmp_end = strchr(tmp_end, delim)) != NULL && (tmp_end[1] == delim)) { tmp_end = (char *) &tmp_end[2]; } /* Reset var options to point to next element */ if (tmp_end) { tmp_end[0] = '\0'; options = (char *) &tmp_end[1]; } else /* Reached the end of the mount option string */ options = end; /* Now build new password string */ temp_len = strlen(value); vol->password = kzalloc(temp_len+1, GFP_KERNEL); if (vol->password == NULL) { printk(KERN_WARNING "CIFS: no memory " "for password\n"); goto cifs_parse_mount_err; } for (i = 0, j = 0; i < temp_len; i++, j++) { vol->password[j] = value[i]; if ((value[i] == delim) && value[i+1] == delim) /* skip the second deliminator */ i++; } vol->password[j] = '\0'; break; case Opt_blank_ip: vol->UNCip = NULL; break; case Opt_ip: string = match_strdup(args); if (string == NULL) goto out_nomem; if (strnlen(string, INET6_ADDRSTRLEN) > INET6_ADDRSTRLEN) { printk(KERN_WARNING "CIFS: ip address " "too long\n"); goto cifs_parse_mount_err; } vol->UNCip = kstrdup(string, GFP_KERNEL); if (!vol->UNCip) { printk(KERN_WARNING "CIFS: no memory " "for UNC IP\n"); goto cifs_parse_mount_err; } break; case Opt_unc: string = match_strdup(args); if (string == NULL) goto out_nomem; temp_len = strnlen(string, 300); if (temp_len == 300) { printk(KERN_WARNING "CIFS: UNC name too long\n"); goto cifs_parse_mount_err; } vol->UNC = kmalloc(temp_len+1, GFP_KERNEL); if (vol->UNC == NULL) { printk(KERN_WARNING "CIFS: no memory for UNC\n"); goto cifs_parse_mount_err; } strcpy(vol->UNC, string); if (strncmp(string, "//", 2) == 0) { vol->UNC[0] = '\\'; vol->UNC[1] = '\\'; } else if (strncmp(string, "\\\\", 2) != 0) { printk(KERN_WARNING "CIFS: UNC Path does not " "begin with // or \\\\\n"); goto cifs_parse_mount_err; } break; case Opt_domain: string = match_strdup(args); if (string == NULL) goto out_nomem; if (strnlen(string, 256) == 256) { printk(KERN_WARNING "CIFS: domain name too" " long\n"); goto cifs_parse_mount_err; } vol->domainname = kstrdup(string, GFP_KERNEL); if (!vol->domainname) { printk(KERN_WARNING "CIFS: no memory " "for domainname\n"); goto cifs_parse_mount_err; } cFYI(1, "Domain name set"); break; case Opt_srcaddr: string = match_strdup(args); if (string == NULL) goto out_nomem; if (!cifs_convert_address( (struct sockaddr *)&vol->srcaddr, string, strlen(string))) { printk(KERN_WARNING "CIFS: Could not parse" " srcaddr: %s\n", string); goto cifs_parse_mount_err; } break; case Opt_prefixpath: string = match_strdup(args); if (string == NULL) goto out_nomem; temp_len = strnlen(string, 1024); if (string[0] != '/') temp_len++; /* missing leading slash */ if (temp_len > 1024) { printk(KERN_WARNING "CIFS: prefix too long\n"); goto cifs_parse_mount_err; } vol->prepath = kmalloc(temp_len+1, GFP_KERNEL); if (vol->prepath == NULL) { printk(KERN_WARNING "CIFS: no memory " "for path prefix\n"); goto cifs_parse_mount_err; } if (string[0] != '/') { vol->prepath[0] = '/'; strcpy(vol->prepath+1, string); } else strcpy(vol->prepath, string); break; case Opt_iocharset: string = match_strdup(args); if (string == NULL) goto out_nomem; if (strnlen(string, 1024) >= 65) { printk(KERN_WARNING "CIFS: iocharset name " "too long.\n"); goto cifs_parse_mount_err; } if (strnicmp(string, "default", 7) != 0) { vol->iocharset = kstrdup(string, GFP_KERNEL); if (!vol->iocharset) { printk(KERN_WARNING "CIFS: no memory" "for charset\n"); goto cifs_parse_mount_err; } } /* if iocharset not set then load_nls_default * is used by caller */ cFYI(1, "iocharset set to %s", string); break; case Opt_sockopt: string = match_strdup(args); if (string == NULL) goto out_nomem; if (strnicmp(string, "TCP_NODELAY", 11) == 0) vol->sockopt_tcp_nodelay = 1; break; case Opt_netbiosname: string = match_strdup(args); if (string == NULL) goto out_nomem; memset(vol->source_rfc1001_name, 0x20, RFC1001_NAME_LEN); /* * FIXME: are there cases in which a comma can * be valid in workstation netbios name (and * need special handling)? */ for (i = 0; i < RFC1001_NAME_LEN; i++) { /* don't ucase netbiosname for user */ if (string[i] == 0) break; vol->source_rfc1001_name[i] = string[i]; } /* The string has 16th byte zero still from * set at top of the function */ if (i == RFC1001_NAME_LEN && string[i] != 0) printk(KERN_WARNING "CIFS: netbiosname" " longer than 15 truncated.\n"); break; case Opt_servern: /* servernetbiosname specified override *SMBSERVER */ string = match_strdup(args); if (string == NULL) goto out_nomem; /* last byte, type, is 0x20 for servr type */ memset(vol->target_rfc1001_name, 0x20, RFC1001_NAME_LEN_WITH_NULL); /* BB are there cases in which a comma can be valid in this workstation netbios name (and need special handling)? */ /* user or mount helper must uppercase the netbios name */ for (i = 0; i < 15; i++) { if (string[i] == 0) break; vol->target_rfc1001_name[i] = string[i]; } /* The string has 16th byte zero still from set at top of the function */ if (i == RFC1001_NAME_LEN && string[i] != 0) printk(KERN_WARNING "CIFS: server net" "biosname longer than 15 truncated.\n"); break; case Opt_ver: string = match_strdup(args); if (string == NULL) goto out_nomem; if (strnicmp(string, "1", 1) == 0) { /* This is the default */ break; } /* For all other value, error */ printk(KERN_WARNING "CIFS: Invalid version" " specified\n"); goto cifs_parse_mount_err; case Opt_vers: string = match_strdup(args); if (string == NULL) goto out_nomem; if (cifs_parse_smb_version(string, vol) != 0) goto cifs_parse_mount_err; break; case Opt_sec: string = match_strdup(args); if (string == NULL) goto out_nomem; if (cifs_parse_security_flavors(string, vol) != 0) goto cifs_parse_mount_err; break; case Opt_cache: cache_specified = true; string = match_strdup(args); if (string == NULL) goto out_nomem; if (cifs_parse_cache_flavor(string, vol) != 0) goto cifs_parse_mount_err; break; default: /* * An option we don't recognize. Save it off for later * if we haven't already found one */ if (!invalid) invalid = data; break; } /* Free up any allocated string */ kfree(string); string = NULL; } if (!sloppy && invalid) { printk(KERN_ERR "CIFS: Unknown mount option \"%s\"\n", invalid); goto cifs_parse_mount_err; } #ifndef CONFIG_KEYS /* Muliuser mounts require CONFIG_KEYS support */ if (vol->multiuser) { cERROR(1, "Multiuser mounts require kernels with " "CONFIG_KEYS enabled."); goto cifs_parse_mount_err; } #endif if (vol->UNCip == NULL) vol->UNCip = &vol->UNC[2]; if (uid_specified) vol->override_uid = override_uid; else if (override_uid == 1) printk(KERN_NOTICE "CIFS: ignoring forceuid mount option " "specified with no uid= option.\n"); if (gid_specified) vol->override_gid = override_gid; else if (override_gid == 1) printk(KERN_NOTICE "CIFS: ignoring forcegid mount option " "specified with no gid= option.\n"); /* FIXME: remove this block in 3.7 */ if (!cache_specified && !cache_warned) { cache_warned = true; printk(KERN_NOTICE "CIFS: no cache= option specified, using " "\"cache=loose\". This default will change " "to \"cache=strict\" in 3.7.\n"); } kfree(mountdata_copy); return 0; out_nomem: printk(KERN_WARNING "Could not allocate temporary buffer\n"); cifs_parse_mount_err: kfree(string); kfree(mountdata_copy); return 1; } /** Returns true if srcaddr isn't specified and rhs isn't * specified, or if srcaddr is specified and * matches the IP address of the rhs argument. */ static bool srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs) { switch (srcaddr->sa_family) { case AF_UNSPEC: return (rhs->sa_family == AF_UNSPEC); case AF_INET: { struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr; struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs; return (saddr4->sin_addr.s_addr == vaddr4->sin_addr.s_addr); } case AF_INET6: { struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr; struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)&rhs; return ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr); } default: WARN_ON(1); return false; /* don't expect to be here */ } } /* * If no port is specified in addr structure, we try to match with 445 port * and if it fails - with 139 ports. It should be called only if address * families of server and addr are equal. */ static bool match_port(struct TCP_Server_Info *server, struct sockaddr *addr) { __be16 port, *sport; switch (addr->sa_family) { case AF_INET: sport = &((struct sockaddr_in *) &server->dstaddr)->sin_port; port = ((struct sockaddr_in *) addr)->sin_port; break; case AF_INET6: sport = &((struct sockaddr_in6 *) &server->dstaddr)->sin6_port; port = ((struct sockaddr_in6 *) addr)->sin6_port; break; default: WARN_ON(1); return false; } if (!port) { port = htons(CIFS_PORT); if (port == *sport) return true; port = htons(RFC1001_PORT); } return port == *sport; } static bool match_address(struct TCP_Server_Info *server, struct sockaddr *addr, struct sockaddr *srcaddr) { switch (addr->sa_family) { case AF_INET: { struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; struct sockaddr_in *srv_addr4 = (struct sockaddr_in *)&server->dstaddr; if (addr4->sin_addr.s_addr != srv_addr4->sin_addr.s_addr) return false; break; } case AF_INET6: { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; struct sockaddr_in6 *srv_addr6 = (struct sockaddr_in6 *)&server->dstaddr; if (!ipv6_addr_equal(&addr6->sin6_addr, &srv_addr6->sin6_addr)) return false; if (addr6->sin6_scope_id != srv_addr6->sin6_scope_id) return false; break; } default: WARN_ON(1); return false; /* don't expect to be here */ } if (!srcip_matches(srcaddr, (struct sockaddr *)&server->srcaddr)) return false; return true; } static bool match_security(struct TCP_Server_Info *server, struct smb_vol *vol) { unsigned int secFlags; if (vol->secFlg & (~(CIFSSEC_MUST_SIGN | CIFSSEC_MUST_SEAL))) secFlags = vol->secFlg; else secFlags = global_secflags | vol->secFlg; switch (server->secType) { case LANMAN: if (!(secFlags & (CIFSSEC_MAY_LANMAN|CIFSSEC_MAY_PLNTXT))) return false; break; case NTLMv2: if (!(secFlags & CIFSSEC_MAY_NTLMV2)) return false; break; case NTLM: if (!(secFlags & CIFSSEC_MAY_NTLM)) return false; break; case Kerberos: if (!(secFlags & CIFSSEC_MAY_KRB5)) return false; break; case RawNTLMSSP: if (!(secFlags & CIFSSEC_MAY_NTLMSSP)) return false; break; default: /* shouldn't happen */ return false; } /* now check if signing mode is acceptable */ if ((secFlags & CIFSSEC_MAY_SIGN) == 0 && (server->sec_mode & SECMODE_SIGN_REQUIRED)) return false; else if (((secFlags & CIFSSEC_MUST_SIGN) == CIFSSEC_MUST_SIGN) && (server->sec_mode & (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED)) == 0) return false; return true; } static int match_server(struct TCP_Server_Info *server, struct sockaddr *addr, struct smb_vol *vol) { if ((server->vals != vol->vals) || (server->ops != vol->ops)) return 0; if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns)) return 0; if (!match_address(server, addr, (struct sockaddr *)&vol->srcaddr)) return 0; if (!match_port(server, addr)) return 0; if (!match_security(server, vol)) return 0; return 1; } static struct TCP_Server_Info * cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) { struct TCP_Server_Info *server; spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { if (!match_server(server, addr, vol)) continue; ++server->srv_count; spin_unlock(&cifs_tcp_ses_lock); cFYI(1, "Existing tcp session with server found"); return server; } spin_unlock(&cifs_tcp_ses_lock); return NULL; } static void cifs_put_tcp_session(struct TCP_Server_Info *server) { struct task_struct *task; spin_lock(&cifs_tcp_ses_lock); if (--server->srv_count > 0) { spin_unlock(&cifs_tcp_ses_lock); return; } put_net(cifs_net_ns(server)); list_del_init(&server->tcp_ses_list); spin_unlock(&cifs_tcp_ses_lock); cancel_delayed_work_sync(&server->echo); spin_lock(&GlobalMid_Lock); server->tcpStatus = CifsExiting; spin_unlock(&GlobalMid_Lock); cifs_crypto_shash_release(server); cifs_fscache_release_client_cookie(server); kfree(server->session_key.response); server->session_key.response = NULL; server->session_key.len = 0; task = xchg(&server->tsk, NULL); if (task) force_sig(SIGKILL, task); } static struct TCP_Server_Info * cifs_get_tcp_session(struct smb_vol *volume_info) { struct TCP_Server_Info *tcp_ses = NULL; struct sockaddr_storage addr; struct sockaddr_in *sin_server = (struct sockaddr_in *) &addr; struct sockaddr_in6 *sin_server6 = (struct sockaddr_in6 *) &addr; int rc; memset(&addr, 0, sizeof(struct sockaddr_storage)); cFYI(1, "UNC: %s ip: %s", volume_info->UNC, volume_info->UNCip); if (volume_info->UNCip && volume_info->UNC) { rc = cifs_fill_sockaddr((struct sockaddr *)&addr, volume_info->UNCip, strlen(volume_info->UNCip), volume_info->port); if (!rc) { /* we failed translating address */ rc = -EINVAL; goto out_err; } } else if (volume_info->UNCip) { /* BB using ip addr as tcp_ses name to connect to the DFS root below */ cERROR(1, "Connecting to DFS root not implemented yet"); rc = -EINVAL; goto out_err; } else /* which tcp_sess DFS root would we conect to */ { cERROR(1, "CIFS mount error: No UNC path (e.g. -o " "unc=//192.168.1.100/public) specified"); rc = -EINVAL; goto out_err; } /* see if we already have a matching tcp_ses */ tcp_ses = cifs_find_tcp_session((struct sockaddr *)&addr, volume_info); if (tcp_ses) return tcp_ses; tcp_ses = kzalloc(sizeof(struct TCP_Server_Info), GFP_KERNEL); if (!tcp_ses) { rc = -ENOMEM; goto out_err; } rc = cifs_crypto_shash_allocate(tcp_ses); if (rc) { cERROR(1, "could not setup hash structures rc %d", rc); goto out_err; } tcp_ses->ops = volume_info->ops; tcp_ses->vals = volume_info->vals; cifs_set_net_ns(tcp_ses, get_net(current->nsproxy->net_ns)); tcp_ses->hostname = extract_hostname(volume_info->UNC); if (IS_ERR(tcp_ses->hostname)) { rc = PTR_ERR(tcp_ses->hostname); goto out_err_crypto_release; } tcp_ses->noblocksnd = volume_info->noblocksnd; tcp_ses->noautotune = volume_info->noautotune; tcp_ses->tcp_nodelay = volume_info->sockopt_tcp_nodelay; tcp_ses->in_flight = 0; tcp_ses->credits = 1; init_waitqueue_head(&tcp_ses->response_q); init_waitqueue_head(&tcp_ses->request_q); INIT_LIST_HEAD(&tcp_ses->pending_mid_q); mutex_init(&tcp_ses->srv_mutex); memcpy(tcp_ses->workstation_RFC1001_name, volume_info->source_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL); memcpy(tcp_ses->server_RFC1001_name, volume_info->target_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL); tcp_ses->session_estab = false; tcp_ses->sequence_number = 0; tcp_ses->lstrp = jiffies; spin_lock_init(&tcp_ses->req_lock); INIT_LIST_HEAD(&tcp_ses->tcp_ses_list); INIT_LIST_HEAD(&tcp_ses->smb_ses_list); INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request); /* * at this point we are the only ones with the pointer * to the struct since the kernel thread not created yet * no need to spinlock this init of tcpStatus or srv_count */ tcp_ses->tcpStatus = CifsNew; memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr, sizeof(tcp_ses->srcaddr)); ++tcp_ses->srv_count; if (addr.ss_family == AF_INET6) { cFYI(1, "attempting ipv6 connect"); /* BB should we allow ipv6 on port 139? */ /* other OS never observed in Wild doing 139 with v6 */ memcpy(&tcp_ses->dstaddr, sin_server6, sizeof(struct sockaddr_in6)); } else memcpy(&tcp_ses->dstaddr, sin_server, sizeof(struct sockaddr_in)); rc = ip_connect(tcp_ses); if (rc < 0) { cERROR(1, "Error connecting to socket. Aborting operation"); goto out_err_crypto_release; } /* * since we're in a cifs function already, we know that * this will succeed. No need for try_module_get(). */ __module_get(THIS_MODULE); tcp_ses->tsk = kthread_run(cifs_demultiplex_thread, tcp_ses, "cifsd"); if (IS_ERR(tcp_ses->tsk)) { rc = PTR_ERR(tcp_ses->tsk); cERROR(1, "error %d create cifsd thread", rc); module_put(THIS_MODULE); goto out_err_crypto_release; } tcp_ses->tcpStatus = CifsNeedNegotiate; /* thread spawned, put it on the list */ spin_lock(&cifs_tcp_ses_lock); list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list); spin_unlock(&cifs_tcp_ses_lock); cifs_fscache_get_client_cookie(tcp_ses); /* queue echo request delayed work */ queue_delayed_work(cifsiod_wq, &tcp_ses->echo, SMB_ECHO_INTERVAL); return tcp_ses; out_err_crypto_release: cifs_crypto_shash_release(tcp_ses); put_net(cifs_net_ns(tcp_ses)); out_err: if (tcp_ses) { if (!IS_ERR(tcp_ses->hostname)) kfree(tcp_ses->hostname); if (tcp_ses->ssocket) sock_release(tcp_ses->ssocket); kfree(tcp_ses); } return ERR_PTR(rc); } static int match_session(struct cifs_ses *ses, struct smb_vol *vol) { switch (ses->server->secType) { case Kerberos: if (vol->cred_uid != ses->cred_uid) return 0; break; default: /* NULL username means anonymous session */ if (ses->user_name == NULL) { if (!vol->nullauth) return 0; break; } /* anything else takes username/password */ if (strncmp(ses->user_name, vol->username ? vol->username : "", MAX_USERNAME_SIZE)) return 0; if (strlen(vol->username) != 0 && ses->password != NULL && strncmp(ses->password, vol->password ? vol->password : "", MAX_PASSWORD_SIZE)) return 0; } return 1; } static struct cifs_ses * cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol) { struct cifs_ses *ses; spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { if (!match_session(ses, vol)) continue; ++ses->ses_count; spin_unlock(&cifs_tcp_ses_lock); return ses; } spin_unlock(&cifs_tcp_ses_lock); return NULL; } static void cifs_put_smb_ses(struct cifs_ses *ses) { int xid; struct TCP_Server_Info *server = ses->server; cFYI(1, "%s: ses_count=%d\n", __func__, ses->ses_count); spin_lock(&cifs_tcp_ses_lock); if (--ses->ses_count > 0) { spin_unlock(&cifs_tcp_ses_lock); return; } list_del_init(&ses->smb_ses_list); spin_unlock(&cifs_tcp_ses_lock); if (ses->status == CifsGood) { xid = GetXid(); CIFSSMBLogoff(xid, ses); _FreeXid(xid); } sesInfoFree(ses); cifs_put_tcp_session(server); } #ifdef CONFIG_KEYS /* strlen("cifs:a:") + INET6_ADDRSTRLEN + 1 */ #define CIFSCREDS_DESC_SIZE (7 + INET6_ADDRSTRLEN + 1) /* Populate username and pw fields from keyring if possible */ static int cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses) { int rc = 0; char *desc, *delim, *payload; ssize_t len; struct key *key; struct TCP_Server_Info *server = ses->server; struct sockaddr_in *sa; struct sockaddr_in6 *sa6; struct user_key_payload *upayload; desc = kmalloc(CIFSCREDS_DESC_SIZE, GFP_KERNEL); if (!desc) return -ENOMEM; /* try to find an address key first */ switch (server->dstaddr.ss_family) { case AF_INET: sa = (struct sockaddr_in *)&server->dstaddr; sprintf(desc, "cifs:a:%pI4", &sa->sin_addr.s_addr); break; case AF_INET6: sa6 = (struct sockaddr_in6 *)&server->dstaddr; sprintf(desc, "cifs:a:%pI6c", &sa6->sin6_addr.s6_addr); break; default: cFYI(1, "Bad ss_family (%hu)", server->dstaddr.ss_family); rc = -EINVAL; goto out_err; } cFYI(1, "%s: desc=%s", __func__, desc); key = request_key(&key_type_logon, desc, ""); if (IS_ERR(key)) { if (!ses->domainName) { cFYI(1, "domainName is NULL"); rc = PTR_ERR(key); goto out_err; } /* didn't work, try to find a domain key */ sprintf(desc, "cifs:d:%s", ses->domainName); cFYI(1, "%s: desc=%s", __func__, desc); key = request_key(&key_type_logon, desc, ""); if (IS_ERR(key)) { rc = PTR_ERR(key); goto out_err; } } down_read(&key->sem); upayload = key->payload.data; if (IS_ERR_OR_NULL(upayload)) { rc = upayload ? PTR_ERR(upayload) : -EINVAL; goto out_key_put; } /* find first : in payload */ payload = (char *)upayload->data; delim = strnchr(payload, upayload->datalen, ':'); cFYI(1, "payload=%s", payload); if (!delim) { cFYI(1, "Unable to find ':' in payload (datalen=%d)", upayload->datalen); rc = -EINVAL; goto out_key_put; } len = delim - payload; if (len > MAX_USERNAME_SIZE || len <= 0) { cFYI(1, "Bad value from username search (len=%zd)", len); rc = -EINVAL; goto out_key_put; } vol->username = kstrndup(payload, len, GFP_KERNEL); if (!vol->username) { cFYI(1, "Unable to allocate %zd bytes for username", len); rc = -ENOMEM; goto out_key_put; } cFYI(1, "%s: username=%s", __func__, vol->username); len = key->datalen - (len + 1); if (len > MAX_PASSWORD_SIZE || len <= 0) { cFYI(1, "Bad len for password search (len=%zd)", len); rc = -EINVAL; kfree(vol->username); vol->username = NULL; goto out_key_put; } ++delim