From c3250149e12338fac5093991b385ad2807c92d1f Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 30 Oct 2007 16:22:24 -0700 Subject: [PATCH] Add new parameter, "min receivefile size" (by default set to zero). If non-zero, writeX calls greater than this value will be left in the socket buffer for later handling with recvfile (or userspace equivalent). Definition of recvfile for your system is left as an exercise for the reader (I'm working on getting splice working :-). Jeremy. (This used to be commit 11c03b75ddbcb6e36b231bb40a1773d1c550621c) --- source3/include/smb.h | 1 + source3/lib/recvfile.c | 40 +++++- source3/lib/util_sock.c | 106 +------------- source3/param/loadparm.c | 5 + source3/smbd/blocking.c | 4 +- source3/smbd/fileio.c | 41 ++++-- source3/smbd/notify.c | 2 +- source3/smbd/process.c | 62 +++++--- source3/smbd/reply.c | 95 +++++++++++-- source3/smbd/server.c | 299 ++++++++++++++++++++++++++++++++++++++- source3/smbd/vfs.c | 32 ++++- 11 files changed, 527 insertions(+), 160 deletions(-) diff --git a/source3/include/smb.h b/source3/include/smb.h index 4c51acf6f41..303f7606d3d 100644 --- a/source3/include/smb.h +++ b/source3/include/smb.h @@ -675,6 +675,7 @@ struct smb_request { uint8 wct; const uint8 *inbuf; uint8 *outbuf; + size_t unread_bytes; }; /* Defines for the sent_oplock_break field above. */ diff --git a/source3/lib/recvfile.c b/source3/lib/recvfile.c index cd9fb12716a..9d77f94a291 100644 --- a/source3/lib/recvfile.c +++ b/source3/lib/recvfile.c @@ -125,13 +125,49 @@ static ssize_t default_sys_recvfile(int fromfd, } #if defined(HAVE_SPLICE_SYSCALL) + +#ifdef JRA_SPLICE_TEST +#include +#include + +#define __NR_splice 313 +_syscall6( long, splice, + int, fromfd, + loff_t *, fromoffset, + int, tofd, + loff_t *, tooffset, + size_t, count, + unsigned int, flags); +#endif + ssize_t sys_recvfile(int fromfd, int tofd, SMB_OFF_T offset, size_t count) { - errno = ENOSYS - return -1; + size_t total = 0; + + if (count == 0) { + return 0; + } + + while (total < count) { + ssize_t ret = splice(fromfd, + NULL, + tofd, + &offset, + count, + 0); + if (ret == -1) { + if (errno != EINTR) { + return -1; + } + continue; + } + total += ret; + count -= ret; + } + return total; } #else diff --git a/source3/lib/util_sock.c b/source3/lib/util_sock.c index e66bd5f15a1..bbcbcacb4ab 100644 --- a/source3/lib/util_sock.c +++ b/source3/lib/util_sock.c @@ -1113,7 +1113,7 @@ bool send_keepalive(int client) Timeout is in milliseconds. ****************************************************************************/ -static ssize_t read_smb_length_return_keepalive(int fd, +ssize_t read_smb_length_return_keepalive(int fd, char *inbuf, unsigned int timeout) { @@ -1260,86 +1260,6 @@ ssize_t receive_smb_raw(int fd, return len; } -static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd, - char **buffer, unsigned int timeout) -{ - char lenbuf[4]; - ssize_t len,ret; - - smb_read_error = 0; - - len = read_smb_length_return_keepalive(fd, lenbuf, timeout); - if (len < 0) { - DEBUG(10,("receive_smb_raw: length < 0!\n")); - - /* - * Correct fix. smb_read_error may have already been - * set. Only set it here if not already set. Global - * variables still suck :-). JRA. - */ - - if (smb_read_error == 0) - smb_read_error = READ_ERROR; - return -1; - } - - /* - * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes - * of header. Don't print the error if this fits.... JRA. - */ - - if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) { - DEBUG(0,("Invalid packet length! (%lu bytes).\n", - (unsigned long)len)); - if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) { - - /* - * Correct fix. smb_read_error may have already been - * set. Only set it here if not already set. Global - * variables still suck :-). JRA. - */ - - if (smb_read_error == 0) - smb_read_error = READ_ERROR; - return -1; - } - } - - /* - * The +4 here can't wrap, we've checked the length above already. - */ - - *buffer = TALLOC_ARRAY(mem_ctx, char, len+4); - - if (*buffer == NULL) { - DEBUG(0, ("Could not allocate inbuf of length %d\n", - (int)len+4)); - if (smb_read_error == 0) - smb_read_error = READ_ERROR; - return -1; - } - - memcpy(*buffer, lenbuf, sizeof(lenbuf)); - - if(len > 0) { - if (timeout > 0) { - ret = read_socket_with_timeout(fd,(*buffer)+4, len, - len, timeout); - } else { - ret = read_data(fd, (*buffer)+4, len); - } - - if (ret != len) { - if (smb_read_error == 0) { - smb_read_error = READ_ERROR; - } - return -1; - } - } - - return len + 4; -} - /**************************************************************************** Wrapper for receive_smb_raw(). Checks the MAC on signed packets. @@ -1364,30 +1284,6 @@ bool receive_smb(int fd, char *buffer, unsigned int timeout) return true; } -ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer, - unsigned int timeout) -{ - ssize_t len; - - len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout); - - if (len < 0) { - return -1; - } - - /* Check the incoming SMB signature. */ - if (!srv_check_sign_mac(*buffer, true)) { - DEBUG(0, ("receive_smb: SMB Signature verification failed on " - "incoming packet!\n")); - if (smb_read_error == 0) { - smb_read_error = READ_BAD_SIG; - } - return -1; - } - - return len; -} - /**************************************************************************** Send an smb to a fd. ****************************************************************************/ diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index dcec6bce897..a5b26475679 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -331,6 +331,7 @@ typedef struct { bool bResetOnZeroVC; int iKeepalive; + int iminreceivefile; param_opt_struct *param_opt; } global; @@ -998,6 +999,7 @@ static struct parm_struct parm_table[] = { {"max protocol", P_ENUM, P_GLOBAL, &Globals.maxprotocol, NULL, enum_protocol, FLAG_ADVANCED}, {"protocol", P_ENUM, P_GLOBAL, &Globals.maxprotocol, NULL, enum_protocol, FLAG_ADVANCED}, {"min protocol", P_ENUM, P_GLOBAL, &Globals.minprotocol, NULL, enum_protocol, FLAG_ADVANCED}, + {"min receivefile size", P_INTEGER, P_GLOBAL, &Globals.iminreceivefile, NULL, NULL, FLAG_ADVANCED}, {"read raw", P_BOOL, P_GLOBAL, &Globals.bReadRaw, NULL, NULL, FLAG_ADVANCED}, {"write raw", P_BOOL, P_GLOBAL, &Globals.bWriteRaw, NULL, NULL, FLAG_ADVANCED}, {"disable netbios", P_BOOL, P_GLOBAL, &Globals.bDisableNetbios, NULL, NULL, FLAG_ADVANCED}, @@ -1708,6 +1710,8 @@ static void init_globals(bool first_time_only) /* By default no shares out of the registry */ Globals.bRegistryShares = False; + + Globals.iminreceivefile = 0; } /******************************************************************* @@ -2165,6 +2169,7 @@ FN_GLOBAL_INTEGER(lp_algorithmic_rid_base, &Globals.AlgorithmicRidBase) FN_GLOBAL_INTEGER(lp_name_cache_timeout, &Globals.name_cache_timeout) FN_GLOBAL_INTEGER(lp_client_signing, &Globals.client_signing) FN_GLOBAL_INTEGER(lp_server_signing, &Globals.server_signing) +FN_GLOBAL_INTEGER(lp_min_receive_file_size, &Globals.iminreceivefile); FN_GLOBAL_INTEGER(lp_client_ldap_sasl_wrapping, &Globals.client_ldap_sasl_wrapping) /* local prototypes */ diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c index 66a61449a1d..0078bb7d13d 100644 --- a/source3/smbd/blocking.c +++ b/source3/smbd/blocking.c @@ -259,7 +259,7 @@ static void reply_lockingX_success(blocking_lock_record *blr) smb_panic("Could not allocate smb_request"); } - init_smb_request(req, (uint8 *)blr->inbuf); + init_smb_request(req, (uint8 *)blr->inbuf, 0); reply_outbuf(req, 2, 0); /* @@ -531,7 +531,7 @@ static bool process_trans2(blocking_lock_record *blr) return True; } - init_smb_request(req, (uint8 *)blr->inbuf); + init_smb_request(req, (uint8 *)blr->inbuf, 0); SCVAL(req->inbuf, smb_com, SMBtrans2); SSVAL(params,0,0); diff --git a/source3/smbd/fileio.c b/source3/smbd/fileio.c index 14056ddc803..9e296f2204e 100644 --- a/source3/smbd/fileio.c +++ b/source3/smbd/fileio.c @@ -116,12 +116,16 @@ static unsigned int allocated_write_caches; *Really* write to a file. ****************************************************************************/ -static ssize_t real_write_file(files_struct *fsp,const char *data, SMB_OFF_T pos, size_t n) +static ssize_t real_write_file(struct smb_request *req, + files_struct *fsp, + const char *data, + SMB_OFF_T pos, + size_t n) { ssize_t ret; if (pos == -1) { - ret = vfs_write_data(fsp, data, n); + ret = vfs_write_data(req, fsp, data, n); } else { fsp->fh->pos = pos; if (pos && lp_strict_allocate(SNUM(fsp->conn))) { @@ -129,7 +133,7 @@ static ssize_t real_write_file(files_struct *fsp,const char *data, SMB_OFF_T pos return -1; } } - ret = vfs_pwrite_data(fsp, data, n, pos); + ret = vfs_pwrite_data(req, fsp, data, n, pos); } DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n", @@ -191,11 +195,15 @@ static int wcp_file_size_change(files_struct *fsp) Write to a file. ****************************************************************************/ -ssize_t write_file(files_struct *fsp, const char *data, SMB_OFF_T pos, size_t n) +ssize_t write_file(struct smb_request *req, + files_struct *fsp, + const char *data, + SMB_OFF_T pos, + size_t n) { write_cache *wcp = fsp->wcp; ssize_t total_written = 0; - int write_path = -1; + int write_path = -1; if (fsp->print_file) { fstring sharename; @@ -234,8 +242,8 @@ ssize_t write_file(files_struct *fsp, const char *data, SMB_OFF_T pos, size_t n) if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type) && !wcp) { setup_write_cache(fsp, st.st_size); wcp = fsp->wcp; - } - } + } + } } #ifdef WITH_PROFILE @@ -280,9 +288,18 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n", } #endif + if (wcp && req->unread_bytes) { + /* If we're using receivefile don't + * deal with a write cache. + */ + flush_write_cache(fsp, WRITE_FLUSH); + delete_write_cache(fsp); + wcp = NULL; + } + if(!wcp) { DO_PROFILE_INC(writecache_direct_writes); - total_written = real_write_file(fsp, data, pos, n); + total_written = real_write_file(req, fsp, data, pos, n); return total_written; } @@ -291,7 +308,7 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n", fsp->fh->pos = pos + n; - /* + /* * If we have active cache and it isn't contiguous then we flush. * NOTE: There is a small problem with running out of disk .... */ @@ -610,7 +627,7 @@ len = %u\n",fsp->fh->fd, (double)pos, (unsigned int)n, (double)wcp->offset, (uns if ( n <= wcp->alloc_size && n > wcp->data_size) { cache_flush_needed = True; } else { - ssize_t ret = real_write_file(fsp, data, pos, n); + ssize_t ret = real_write_file(NULL,fsp, data, pos, n); /* * If the write overlaps the entire cache, then @@ -657,7 +674,7 @@ n = %u, wcp->offset=%.0f, wcp->data_size=%u\n", */ if (n > wcp->alloc_size ) { - ssize_t ret = real_write_file(fsp, data, pos, n); + ssize_t ret = real_write_file(NULL,fsp, data, pos, n); if (ret == -1) { return -1; } @@ -828,7 +845,7 @@ ssize_t flush_write_cache(files_struct *fsp, enum flush_reason_enum reason) } #endif - ret = real_write_file(fsp, wcp->data, wcp->offset, data_size); + ret = real_write_file(NULL, fsp, wcp->data, wcp->offset, data_size); /* * Ensure file size if kept up to date if write extends file. diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c index ecb7d9dce8e..0dd7fbb20e9 100644 --- a/source3/smbd/notify.c +++ b/source3/smbd/notify.c @@ -189,7 +189,7 @@ void change_notify_reply(const uint8 *request_buf, uint32 max_param, smb_setlen((char *)tmp_request, smb_size); SCVAL(tmp_request, smb_wct, 0); - init_smb_request(req, tmp_request); + init_smb_request(req, tmp_request,0); send_nt_replies(req, NT_STATUS_OK, prs_data_p(&ps), prs_offset(&ps), NULL, 0); diff --git a/source3/smbd/process.c b/source3/smbd/process.c index ed1bf762e99..1c8d8a6e769 100644 --- a/source3/smbd/process.c +++ b/source3/smbd/process.c @@ -25,7 +25,7 @@ extern int smb_echo_count; const int total_buffer_size = (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE + SAFETY_MARGIN); -/* +/* * Size of data we can send to client. Set * by the client for all protocols above CORE. * Set by us for CORE protocol. @@ -48,7 +48,9 @@ extern int max_send; * Initialize a struct smb_request from an inbuf */ -void init_smb_request(struct smb_request *req, const uint8 *inbuf) +void init_smb_request(struct smb_request *req, + const uint8 *inbuf, + size_t unread_bytes) { size_t req_size = smb_len(inbuf) + 4; /* Ensure we have at least smb_size bytes. */ @@ -63,6 +65,8 @@ void init_smb_request(struct smb_request *req, const uint8 *inbuf) req->vuid = SVAL(inbuf, smb_uid); req->tid = SVAL(inbuf, smb_tid); req->wct = CVAL(inbuf, smb_wct); + req->unread_bytes = unread_bytes; + /* Ensure we have at least wct words and 2 bytes of bcc. */ if (smb_size + req->wct*2 > req_size) { DEBUG(0,("init_smb_request: invalid wct number %u (size %u)\n", @@ -231,6 +235,14 @@ bool push_deferred_smb_message(struct smb_request *req, { struct timeval end_time; + if (req->unread_bytes) { + DEBUG(0,("push_deferred_smb_message: logic error ! " + "unread_bytes = %u\n", + (unsigned int)req->unread_bytes )); + smb_panic("push_deferred_smb_message: " + "logic error unread_bytes != 0" ); + } + end_time = timeval_sum(&request_time, &timeout); DEBUG(10,("push_deferred_open_smb_message: pushing message len %u mid %u " @@ -382,8 +394,11 @@ static int select_on_fd(int fd, int maxfd, fd_set *fds) The timeout is in milliseconds ****************************************************************************/ -static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, - size_t *buffer_len, int timeout) +static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, + char **buffer, + size_t *buffer_len, + int timeout, + size_t *p_unread) { fd_set r_fds, w_fds; int selrtn; @@ -391,6 +406,7 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, int maxfd = 0; ssize_t len; + *p_unread = 0; smb_read_error = 0; again: @@ -565,7 +581,7 @@ static bool receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, goto again; } - len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0); + len = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0, p_unread); if (len == -1) { return False; @@ -1115,7 +1131,7 @@ static void switch_message(uint8 type, struct smb_request *req, int size) Construct a reply to the incoming packet. ****************************************************************************/ -static void construct_reply(char *inbuf, int size) +static void construct_reply(char *inbuf, int size, size_t unread_bytes) { uint8 type = CVAL(inbuf,smb_com); struct smb_request *req; @@ -1127,10 +1143,19 @@ static void construct_reply(char *inbuf, int size) if (!(req = talloc(talloc_tos(), struct smb_request))) { smb_panic("could not allocate smb_request"); } - init_smb_request(req, (uint8 *)inbuf); + init_smb_request(req, (uint8 *)inbuf, unread_bytes); switch_message(type, req, size); + if (req->unread_bytes) { + /* writeX failed. drain socket. */ + if (drain_socket(smbd_server_fd(), req->unread_bytes) != + req->unread_bytes) { + smb_panic("failed to drain pending bytes"); + } + req->unread_bytes = 0; + } + if (req->outbuf == NULL) { return; } @@ -1152,7 +1177,7 @@ static void construct_reply(char *inbuf, int size) Process an smb from the client ****************************************************************************/ -static void process_smb(char *inbuf, size_t nread) +static void process_smb(char *inbuf, size_t nread, size_t unread_bytes) { static int trans_num; int msg_type = CVAL(inbuf,0); @@ -1176,7 +1201,9 @@ static void process_smb(char *inbuf, size_t nread) DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type, smb_len(inbuf) ) ); - DEBUG( 3, ( "Transaction %d of length %d\n", trans_num, (int)nread ) ); + DEBUG( 3, ( "Transaction %d of length %d (%u toread)\n", trans_num, + (int)nread, + (unsigned int)unread_bytes )); if (msg_type != 0) { /* @@ -1188,8 +1215,8 @@ static void process_smb(char *inbuf, size_t nread) show_msg(inbuf); - construct_reply(inbuf,nread); - + construct_reply(inbuf,nread,unread_bytes); + trans_num++; } @@ -1348,7 +1375,7 @@ void chain_reply(struct smb_request *req) if (!(req2 = talloc(talloc_tos(), struct smb_request))) { smb_panic("could not allocate smb_request"); } - init_smb_request(req2, (uint8 *)inbuf2); + init_smb_request(req2, (uint8 *)inbuf2,0); /* process the request */ switch_message(smb_com2, req2, new_size); @@ -1625,6 +1652,7 @@ void smbd_process(void) { time_t last_timeout_processing_time = time(NULL); unsigned int num_smbs = 0; + size_t unread_bytes = 0; max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); @@ -1635,8 +1663,8 @@ void smbd_process(void) size_t inbuf_len; TALLOC_CTX *frame = talloc_stackframe(); - errno = 0; - + errno = 0; + /* Did someone ask for immediate checks on things like blocking locks ? */ if (select_timeout == 0) { if(!timeout_processing(&select_timeout, @@ -1648,7 +1676,7 @@ void smbd_process(void) run_events(smbd_event_context(), 0, NULL, NULL); while (!receive_message_or_smb(NULL, &inbuf, &inbuf_len, - select_timeout)) { + select_timeout, &unread_bytes)) { if(!timeout_processing(&select_timeout, &last_timeout_processing_time)) return; @@ -1664,10 +1692,10 @@ void smbd_process(void) * faster than the select timeout, thus starving out the * essential processing (change notify, blocking locks) that * the timeout code does. JRA. - */ + */ num_echos = smb_echo_count; - process_smb(inbuf, inbuf_len); + process_smb(inbuf, inbuf_len, unread_bytes); TALLOC_FREE(inbuf); diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c index 38ce797eeb2..4c1ed56d4ff 100644 --- a/source3/smbd/reply.c +++ b/source3/smbd/reply.c @@ -3494,7 +3494,7 @@ void reply_writebraw(connection_struct *conn, struct smb_request *req) } if (numtowrite>0) { - nwritten = write_file(fsp,data,startpos,numtowrite); + nwritten = write_file(req,fsp,data,startpos,numtowrite); } DEBUG(3,("reply_writebraw: initial write fnum=%d start=%.0f num=%d " @@ -3572,7 +3572,7 @@ void reply_writebraw(connection_struct *conn, struct smb_request *req) exit_server_cleanly("secondary writebraw failed"); } - nwritten = write_file(fsp,buf+4,startpos+nwritten,numtowrite); + nwritten = write_file(req,fsp,buf+4,startpos+nwritten,numtowrite); if (nwritten == -1) { TALLOC_FREE(buf); reply_unixerror(req, ERRHRD, ERRdiskfull); @@ -3686,7 +3686,7 @@ void reply_writeunlock(connection_struct *conn, struct smb_request *req) if(numtowrite == 0) { nwritten = 0; } else { - nwritten = write_file(fsp,data,startpos,numtowrite); + nwritten = write_file(req,fsp,data,startpos,numtowrite); } status = sync_file(conn, fsp, False /* write through */); @@ -3808,7 +3808,7 @@ void reply_write(connection_struct *conn, struct smb_request *req) return; } } else - nwritten = write_file(fsp,data,startpos,numtowrite); + nwritten = write_file(req,fsp,data,startpos,numtowrite); status = sync_file(conn, fsp, False); if (!NT_STATUS_IS_OK(status)) { @@ -3840,6 +3840,64 @@ void reply_write(connection_struct *conn, struct smb_request *req) return; } +/**************************************************************************** + Ensure a buffer is a valid writeX for recvfile purposes. +****************************************************************************/ + +#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \ + (2*14) + /* word count (including bcc) */ \ + 1 /* pad byte */) + +bool is_valid_writeX_buffer(char *inbuf) +{ + size_t numtowrite; + connection_struct *conn = NULL; + unsigned int doff = 0; + size_t len = smb_len(inbuf); + + if (CVAL(inbuf,smb_com) != SMBwriteX || + CVAL(inbuf,smb_vwv0) != 0xFF || + CVAL(inbuf,smb_wct) != 14) { + return false; + } + conn = conn_find(SVAL(inbuf, smb_tid)); + if (conn == NULL) { + return false; + } + if (IS_IPC(conn)) { + return false; + } + numtowrite = SVAL(inbuf,smb_vwv10); + numtowrite |= ((((size_t)SVAL(inbuf,smb_vwv9)) & 1 )<<16); + if (numtowrite == 0) { + return false; + } + /* Ensure the sizes match up. */ + doff = SVAL(inbuf,smb_vwv11); + + if (doff < STANDARD_WRITE_AND_X_HEADER_SIZE) { + /* no pad byte...old smbclient :-( */ + return false; + } + + if (len - doff != numtowrite) { + DEBUG(10,("is_valid_writeX_buffer: doff mismatch " + "len = %u, doff = %u, numtowrite = %u\n", + (unsigned int)len, + (unsigned int)doff, + (unsigned int)numtowrite )); + return false; + } + + DEBUG(10,("is_valid_writeX_buffer: true " + "len = %u, doff = %u, numtowrite = %u\n", + (unsigned int)len, + (unsigned int)doff, + (unsigned int)numtowrite )); + + return true; +} + /**************************************************************************** Reply to a write and X. ****************************************************************************/ @@ -3875,10 +3933,18 @@ void reply_write_and_X(connection_struct *conn, struct smb_request *req) numtowrite |= ((((size_t)SVAL(req->inbuf,smb_vwv9)) & 1 )<<16); } - if(smb_doff > smblen || (smb_doff + numtowrite > smblen)) { - reply_doserror(req, ERRDOS, ERRbadmem); - END_PROFILE(SMBwriteX); - return; + if (req->unread_bytes) { + if (numtowrite != req->unread_bytes) { + reply_doserror(req, ERRDOS, ERRbadmem); + END_PROFILE(SMBwriteX); + return; + } + } else { + if (smb_doff > smblen || smb_doff + numtowrite > smblen) { + reply_doserror(req, ERRDOS, ERRbadmem); + END_PROFILE(SMBwriteX); + return; + } } /* If it's an IPC, pass off the pipe handler. */ @@ -3947,15 +4013,16 @@ void reply_write_and_X(connection_struct *conn, struct smb_request *req) nwritten = 0; } else { - if (schedule_aio_write_and_X(conn, req, fsp, data, startpos, - numtowrite)) { + if (req->unread_bytes == 0 && + schedule_aio_write_and_X(conn, req, fsp, data, + startpos, numtowrite)) { END_PROFILE(SMBwriteX); return; } - nwritten = write_file(fsp,data,startpos,numtowrite); + nwritten = write_file(req,fsp,data,startpos,numtowrite); } - + if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) { reply_unixerror(req, ERRHRD, ERRdiskfull); END_PROFILE(SMBwriteX); @@ -4264,7 +4331,7 @@ void reply_writeclose(connection_struct *conn, struct smb_request *req) return; } - nwritten = write_file(fsp,data,startpos,numtowrite); + nwritten = write_file(req,fsp,data,startpos,numtowrite); set_filetime(conn, fsp->fsp_name, mtime); @@ -4726,7 +4793,7 @@ void reply_printwrite(connection_struct *conn, struct smb_request *req) data = smb_buf(req->inbuf) + 3; - if (write_file(fsp,data,-1,numtowrite) != numtowrite) { + if (write_file(req,fsp,data,-1,numtowrite) != numtowrite) { reply_unixerror(req, ERRHRD, ERRdiskfull); END_PROFILE(SMBsplwr); return; diff --git a/source3/smbd/server.c b/source3/smbd/server.c index b9ab7ef7ac9..25e2d2cb5e5 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -1,20 +1,22 @@ -/* +/* Unix SMB/CIFS implementation. Main SMB server routines Copyright (C) Andrew Tridgell 1992-1998 Copyright (C) Martin Pool 2002 Copyright (C) Jelmer Vernooij 2002-2003 - + Copyright (C) Volker Lendecke 1993-2007 + Copyright (C) Jeremy Allison 1993-2007 + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -37,6 +39,8 @@ extern SIG_ATOMIC_T got_sig_term; extern SIG_ATOMIC_T reload_after_sighup; static SIG_ATOMIC_T got_sig_cld; +extern int smb_read_error; + #ifdef WITH_DFS extern int dcelogin_atmost_once; #endif /* WITH_DFS */ @@ -60,6 +64,293 @@ static void smbd_set_server_fd(int fd) client_setfd(fd); } +/* Socket functions for smbd packet processing. */ + +static bool valid_packet_size(len) +{ + /* + * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes + * of header. Don't print the error if this fits.... JRA. + */ + + if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) { + DEBUG(0,("Invalid packet length! (%lu bytes).\n", + (unsigned long)len)); + if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) { + + /* + * Correct fix. smb_read_error may have already been + * set. Only set it here if not already set. Global + * variables still suck :-). JRA. + */ + + if (smb_read_error == 0) + smb_read_error = READ_ERROR; + return false; + } + } + return true; +} + +static ssize_t read_packet_remainder(int fd, + char *buffer, + unsigned int timeout, + ssize_t len) +{ + ssize_t ret; + + if(len <= 0) { + return len; + } + + if (timeout > 0) { + ret = read_socket_with_timeout(fd, + buffer, + len, + len, + timeout); + } else { + ret = read_data(fd, buffer, len); + } + + if (ret != len) { + if (smb_read_error == 0) { + smb_read_error = READ_ERROR; + } + return -1; + } + + return len; +} + +/**************************************************************************** + Attempt a zerocopy writeX read. We know here that len > smb_size-4 +****************************************************************************/ + +/* + * Unfortunately, earlier versions of smbclient/libsmbclient + * don't send this "standard" writeX header. I've fixed this + * for 3.2 but we'll use the old method with earlier versions. + * Windows and CIFSFS at least use this standard size. Not + * sure about MacOSX. + */ + +#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \ + (2*14) + /* word count (including bcc) */ \ + 1 /* pad byte */) + +ssize_t receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx, + const char lenbuf[4], + int fd, + char **buffer, + unsigned int timeout, + size_t *p_unread) +{ + /* Size of a WRITEX call (+4 byte len). */ + char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE]; + ssize_t len = smb_len(lenbuf); + ssize_t toread; + ssize_t ret; + + memcpy(writeX_header, lenbuf, sizeof(lenbuf)); + + if (timeout > 0) { + ret = read_socket_with_timeout(fd, + writeX_header + 4, + STANDARD_WRITE_AND_X_HEADER_SIZE, + STANDARD_WRITE_AND_X_HEADER_SIZE, + timeout); + } else { + ret = read_data(fd, + writeX_header+4, + STANDARD_WRITE_AND_X_HEADER_SIZE); + } + + if (ret != STANDARD_WRITE_AND_X_HEADER_SIZE) { + if (smb_read_error == 0) { + smb_read_error = READ_ERROR; + } + return -1; + } + + /* + * Ok - now try and see if this is a possible + * valid writeX call. + */ + + if (is_valid_writeX_buffer(writeX_header)) { + /* + * If the data offset is beyond what + * we've read, drain the extra bytes. + */ + uint16_t doff = SVAL(writeX_header,smb_vwv11); + ssize_t newlen; + + if (doff > STANDARD_WRITE_AND_X_HEADER_SIZE) { + size_t drain = doff - STANDARD_WRITE_AND_X_HEADER_SIZE; + if (drain_socket(smbd_server_fd(), drain) != drain) { + smb_panic("receive_smb_raw_talloc_partial_read:" + " failed to drain pending bytes"); + } + } else { + doff = STANDARD_WRITE_AND_X_HEADER_SIZE; + } + + /* Spoof down the length and null out the bcc. */ + set_message_bcc(writeX_header, 0); + newlen = smb_len(writeX_header); + + /* Copy the header we've written. */ + + *buffer = TALLOC_MEMDUP(mem_ctx, + writeX_header, + sizeof(writeX_header)); + + if (*buffer == NULL) { + DEBUG(0, ("Could not allocate inbuf of length %d\n", + (int)sizeof(writeX_header))); + if (smb_read_error == 0) + smb_read_error = READ_ERROR; + return -1; + } + + /* Work out the remaining bytes. */ + *p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE; + + return newlen + 4; + } + + if (!valid_packet_size(len)) { + return -1; + } + + /* + * Not a valid writeX call. Just do the standard + * talloc and return. + */ + + *buffer = TALLOC_ARRAY(mem_ctx, char, len+4); + + if (*buffer == NULL) { + DEBUG(0, ("Could not allocate inbuf of length %d\n", + (int)len+4)); + if (smb_read_error == 0) + smb_read_error = READ_ERROR; + return -1; + } + + /* Copy in what we already read. */ + memcpy(*buffer, + writeX_header, + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE); + toread = len - STANDARD_WRITE_AND_X_HEADER_SIZE; + + if(toread > 0) { + ret = read_packet_remainder(fd, + (*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE, + timeout, + toread); + if (ret != toread) { + return -1; + } + } + + return len + 4; +} + +static ssize_t receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, + int fd, + char **buffer, + unsigned int timeout, + size_t *p_unread) +{ + char lenbuf[4]; + ssize_t len,ret; + int min_recv_size = lp_min_receive_file_size(); + + smb_read_error = 0; + *p_unread = 0; + + len = read_smb_length_return_keepalive(fd, lenbuf, timeout); + if (len < 0) { + DEBUG(10,("receive_smb_raw: length < 0!\n")); + + /* + * Correct fix. smb_read_error may have already been + * set. Only set it here if not already set. Global + * variables still suck :-). JRA. + */ + + if (smb_read_error == 0) + smb_read_error = READ_ERROR; + return -1; + } + + if (CVAL(lenbuf,0) != SMBkeepalive && + min_recv_size && + len > min_recv_size && + !srv_is_signing_active()) { + + return receive_smb_raw_talloc_partial_read(mem_ctx, + lenbuf, + fd, + buffer, + timeout, + p_unread); + } + + if (!valid_packet_size(len)) { + return -1; + } + + /* + * The +4 here can't wrap, we've checked the length above already. + */ + + *buffer = TALLOC_ARRAY(mem_ctx, char, len+4); + + if (*buffer == NULL) { + DEBUG(0, ("Could not allocate inbuf of length %d\n", + (int)len+4)); + if (smb_read_error == 0) + smb_read_error = READ_ERROR; + return -1; + } + + memcpy(*buffer, lenbuf, sizeof(lenbuf)); + + ret = read_packet_remainder(fd, (*buffer)+4, timeout, len); + if (ret != len) { + return -1; + } + + return len + 4; +} + +ssize_t receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer, + unsigned int timeout, size_t *p_unread) +{ + ssize_t len; + + len = receive_smb_raw_talloc(mem_ctx, fd, buffer, timeout, p_unread); + + if (len < 0) { + return -1; + } + + /* Check the incoming SMB signature. */ + if (!srv_check_sign_mac(*buffer, true)) { + DEBUG(0, ("receive_smb: SMB Signature verification failed on " + "incoming packet!\n")); + if (smb_read_error == 0) { + smb_read_error = READ_BAD_SIG; + } + return -1; + } + + return len; +} + struct event_context *smbd_event_context(void) { static struct event_context *ctx; diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c index e862710b6c4..c1c1939153d 100644 --- a/source3/smbd/vfs.c +++ b/source3/smbd/vfs.c @@ -418,11 +418,24 @@ ssize_t vfs_pread_data(files_struct *fsp, char *buf, Write data to a fd on the vfs. ****************************************************************************/ -ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N) +ssize_t vfs_write_data(struct smb_request *req, + files_struct *fsp, + const char *buffer, + size_t N) { size_t total=0; ssize_t ret; + if (req && req->unread_bytes) { + SMB_ASSERT(req->unread_bytes == N); + req->unread_bytes = 0; + return SMB_VFS_RECVFILE(smbd_server_fd(), + fsp, + fsp->fh->fd, + (SMB_OFF_T)-1, + N); + } + while (total < N) { ret = SMB_VFS_WRITE(fsp,fsp->fh->fd,buffer + total,N - total); @@ -436,12 +449,25 @@ ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N) return (ssize_t)total; } -ssize_t vfs_pwrite_data(files_struct *fsp,const char *buffer, - size_t N, SMB_OFF_T offset) +ssize_t vfs_pwrite_data(struct smb_request *req, + files_struct *fsp, + const char *buffer, + size_t N, + SMB_OFF_T offset) { size_t total=0; ssize_t ret; + if (req && req->unread_bytes) { + SMB_ASSERT(req->unread_bytes == N); + req->unread_bytes = 0; + return SMB_VFS_RECVFILE(smbd_server_fd(), + fsp, + fsp->fh->fd, + offset, + N); + } + while (total < N) { ret = SMB_VFS_PWRITE(fsp, fsp->fh->fd, buffer + total, N - total, offset + total);