X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Fsmbd%2Fprocess.c;h=d617ef19157e864b695eded741136a977dd6fa5d;hb=52f6a4436f9da439fc687894b329898833af7ff8;hp=e4d15d87d60b467cb116022fbd8a78911ae8c8da;hpb=9617a3945b804bf2d1409285df2a7add12690063;p=metze%2Fsamba%2Fwip.git diff --git a/source3/smbd/process.c b/source3/smbd/process.c index e4d15d87d60b..d617ef19157e 100644 --- a/source3/smbd/process.c +++ b/source3/smbd/process.c @@ -19,23 +19,9 @@ */ #include "includes.h" +#include "smbd/globals.h" -/* - * Size of data we can send to client. Set - * by the client for all protocols above CORE. - * Set by us for CORE protocol. - */ -int max_send = BUFFER_SIZE; -/* - * Size of the data we can receive. Set by us. - * Can be modified by the max xmit parameter. - */ -int max_recv = BUFFER_SIZE; - -SIG_ATOMIC_T reload_after_sighup = 0; -SIG_ATOMIC_T got_sig_term = 0; extern bool global_machine_password_needs_changing; -extern int max_send; static void construct_reply_common(struct smb_request *req, const char *inbuf, char *outbuf); @@ -284,7 +270,7 @@ static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd, if (CVAL(lenbuf,0) == 0 && min_recv_size && - smb_len_large(lenbuf) > min_recv_size && /* Could be a UNIX large writeX. */ + smb_len_large(lenbuf) > (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE) && /* Could be a UNIX large writeX. */ !srv_is_signing_active()) { return receive_smb_raw_talloc_partial_read( @@ -386,6 +372,7 @@ void init_smb_request(struct smb_request *req, req->encrypted = encrypted; req->conn = conn_find(req->tid); req->chain_fsp = NULL; + req->chain_outbuf = NULL; /* Ensure we have at least wct words and 2 bytes of bcc. */ if (smb_size + req->wct*2 > req_size) { @@ -406,12 +393,36 @@ void init_smb_request(struct smb_request *req, req->outbuf = NULL; } -/**************************************************************************** - structure to hold a linked list of queued messages. - for processing. -****************************************************************************/ +static void process_smb(struct smbd_server_connection *conn, + uint8_t *inbuf, size_t nread, size_t unread_bytes, + bool encrypted); + +static void smbd_deferred_open_timer(struct event_context *ev, + struct timed_event *te, + struct timeval _tval, + void *private_data) +{ + struct pending_message_list *msg = talloc_get_type(private_data, + struct pending_message_list); + TALLOC_CTX *mem_ctx = talloc_tos(); + uint8_t *inbuf; + + inbuf = (uint8_t *)talloc_memdup(mem_ctx, msg->buf.data, + msg->buf.length); + if (inbuf == NULL) { + exit_server("smbd_deferred_open_timer: talloc failed\n"); + return; + } -static struct pending_message_list *deferred_open_queue; + /* We leave this message on the queue so the open code can + know this is a retry. */ + DEBUG(5,("smbd_deferred_open_timer: trigger mid %u.\n", + (unsigned int)SVAL(msg->buf.data,smb_mid))); + + process_smb(smbd_server_conn, inbuf, + msg->buf.length, 0, + msg->encrypted); +} /**************************************************************************** Function to push a message onto the tail of a linked list of smb messages ready @@ -441,7 +452,6 @@ static bool push_queued_message(struct smb_request *req, } msg->request_time = request_time; - msg->end_time = end_time; msg->encrypted = req->encrypted; if (private_data) { @@ -454,6 +464,17 @@ static bool push_queued_message(struct smb_request *req, } } + msg->te = event_add_timed(smbd_event_context(), + msg, + end_time, + smbd_deferred_open_timer, + msg); + if (!msg->te) { + DEBUG(0,("push_message: event_add_timed failed\n")); + TALLOC_FREE(msg); + return false; + } + DLIST_ADD_END(deferred_open_queue, msg, struct pending_message_list *); DEBUG(10,("push_message: pushed message length %u on " @@ -495,13 +516,29 @@ void schedule_deferred_open_smb_message(uint16 mid) for (pml = deferred_open_queue; pml; pml = pml->next) { uint16 msg_mid = SVAL(pml->buf.data,smb_mid); + DEBUG(10,("schedule_deferred_open_smb_message: [%d] msg_mid = %u\n", i++, (unsigned int)msg_mid )); + if (mid == msg_mid) { + struct timed_event *te; + DEBUG(10,("schedule_deferred_open_smb_message: scheduling mid %u\n", mid )); - pml->end_time.tv_sec = 0; - pml->end_time.tv_usec = 0; + + te = event_add_timed(smbd_event_context(), + pml, + timeval_zero(), + smbd_deferred_open_timer, + pml); + if (!te) { + DEBUG(10,("schedule_deferred_open_smb_message: " + "event_add_timed() failed, skipping mid %u\n", + mid )); + } + + TALLOC_FREE(pml->te); + pml->te = te; DLIST_PROMOTE(deferred_open_queue, pml); return; } @@ -583,26 +620,33 @@ struct idle_event { void *private_data; }; -static void idle_event_handler(struct event_context *ctx, - struct timed_event *te, - const struct timeval *now, - void *private_data) +static void smbd_idle_event_handler(struct event_context *ctx, + struct timed_event *te, + struct timeval now, + void *private_data) { struct idle_event *event = talloc_get_type_abort(private_data, struct idle_event); TALLOC_FREE(event->te); - if (!event->handler(now, event->private_data)) { + DEBUG(10,("smbd_idle_event_handler: %s %p called\n", + event->name, event->te)); + + if (!event->handler(&now, event->private_data)) { + DEBUG(10,("smbd_idle_event_handler: %s %p stopped\n", + event->name, event->te)); /* Don't repeat, delete ourselves */ TALLOC_FREE(event); return; } + DEBUG(10,("smbd_idle_event_handler: %s %p rescheduled\n", + event->name, event->te)); + event->te = event_add_timed(ctx, event, - timeval_sum(now, &event->interval), - event->name, - idle_event_handler, event); + timeval_sum(&now, &event->interval), + smbd_idle_event_handler, event); /* We can't do much but fail here. */ SMB_ASSERT(event->te != NULL); @@ -637,155 +681,77 @@ struct idle_event *event_add_idle(struct event_context *event_ctx, result->te = event_add_timed(event_ctx, result, timeval_sum(&now, &interval), - result->name, - idle_event_handler, result); + smbd_idle_event_handler, result); if (result->te == NULL) { DEBUG(0, ("event_add_timed failed\n")); TALLOC_FREE(result); return NULL; } + DEBUG(10,("event_add_idle: %s %p\n", result->name, result->te)); return result; } -/**************************************************************************** - Do all async processing in here. This includes kernel oplock messages, change - notify events etc. -****************************************************************************/ - -static void async_processing(fd_set *pfds) +static void smbd_sig_term_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) { - DEBUG(10,("async_processing: Doing async processing.\n")); - - process_aio_queue(); - - process_kernel_oplocks(smbd_messaging_context(), pfds); - - /* Do the aio check again after receive_local_message as it does a - select and may have eaten our signal. */ - /* Is this till true? -- vl */ - process_aio_queue(); + exit_server_cleanly("termination signal"); +} - if (got_sig_term) { - exit_server_cleanly("termination signal"); - } +void smbd_setup_sig_term_handler(void) +{ + struct tevent_signal *se; - /* check for sighup processing */ - if (reload_after_sighup) { - change_to_root_user(); - DEBUG(1,("Reloading services after SIGHUP\n")); - reload_services(False); - reload_after_sighup = 0; + se = tevent_add_signal(smbd_event_context(), + smbd_event_context(), + SIGTERM, 0, + smbd_sig_term_handler, + NULL); + if (!se) { + exit_server("failed to setup SIGTERM handler"); } } -/**************************************************************************** - Add a fd to the set we will be select(2)ing on. -****************************************************************************/ - -static int select_on_fd(int fd, int maxfd, fd_set *fds) +static void smbd_sig_hup_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) { - if (fd != -1) { - FD_SET(fd, fds); - maxfd = MAX(maxfd, fd); - } - - return maxfd; + change_to_root_user(); + DEBUG(1,("Reloading services after SIGHUP\n")); + reload_services(False); } -/**************************************************************************** - Do a select on an two fd's - with timeout. - - If a local udp message has been pushed onto the - queue (this can only happen during oplock break - processing) call async_processing() - - If a pending smb message has been pushed onto the - queue (this can only happen during oplock break - processing) return this next. - - If the first smbfd is ready then read an smb from it. - if the second (loopback UDP) fd is ready then read a message - from it and setup the buffer header to identify the length - and from address. - Returns False on timeout or error. - Else returns True. +void smbd_setup_sig_hup_handler(void) +{ + struct tevent_signal *se; -The timeout is in milliseconds -****************************************************************************/ + se = tevent_add_signal(smbd_event_context(), + smbd_event_context(), + SIGHUP, 0, + smbd_sig_hup_handler, + NULL); + if (!se) { + exit_server("failed to setup SIGHUP handler"); + } +} -static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, - size_t *buffer_len, - size_t *p_unread, bool *p_encrypted) +static NTSTATUS smbd_server_connection_loop_once(struct smbd_server_connection *conn) { fd_set r_fds, w_fds; int selrtn; struct timeval to; int maxfd = 0; - size_t len = 0; - NTSTATUS status; - - *p_unread = 0; - - again: to.tv_sec = SMBD_SELECT_TIMEOUT; to.tv_usec = 0; - /* - * Note that this call must be before processing any SMB - * messages as we need to synchronously process any messages - * we may have sent to ourselves from the previous SMB. - */ - message_dispatch(smbd_messaging_context()); - - /* - * Check to see if we already have a message on the deferred open queue - * and it's time to schedule. - */ - if(deferred_open_queue != NULL) { - bool pop_message = False; - struct pending_message_list *msg = deferred_open_queue; - - if (timeval_is_zero(&msg->end_time)) { - pop_message = True; - } else { - struct timeval tv; - int64_t tdif; - - GetTimeOfDay(&tv); - tdif = usec_time_diff(&msg->end_time, &tv); - if (tdif <= 0) { - /* Timed out. Schedule...*/ - pop_message = True; - DEBUG(10,("receive_message_or_smb: queued message timed out.\n")); - } else { - /* Make a more accurate select timeout. */ - to.tv_sec = tdif / 1000000; - to.tv_usec = tdif % 1000000; - DEBUG(10,("receive_message_or_smb: select with timeout of [%u.%06u]\n", - (unsigned int)to.tv_sec, (unsigned int)to.tv_usec )); - } - } - - if (pop_message) { - - *buffer = (char *)talloc_memdup(mem_ctx, msg->buf.data, - msg->buf.length); - if (*buffer == NULL) { - DEBUG(0, ("talloc failed\n")); - return NT_STATUS_NO_MEMORY; - } - *buffer_len = msg->buf.length; - *p_encrypted = msg->encrypted; - - /* We leave this message on the queue so the open code can - know this is a retry. */ - DEBUG(5,("receive_message_or_smb: returning deferred open smb message.\n")); - return NT_STATUS_OK; - } - } - /* * Setup the select fd sets. */ @@ -793,26 +759,6 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, FD_ZERO(&r_fds); FD_ZERO(&w_fds); - /* - * Ensure we process oplock break messages by preference. - * We have to do this before the select, after the select - * and if the select returns EINTR. This is due to the fact - * that the selects called from async_processing can eat an EINTR - * caused by a signal (we can't take the break message there). - * This is hideously complex - *MUST* be simplified for 3.0 ! JRA. - */ - - if (oplock_message_waiting(&r_fds)) { - DEBUG(10,("receive_message_or_smb: oplock_message is waiting.\n")); - async_processing(&r_fds); - /* - * After async processing we must go and do the select again, as - * the state of the flag in fds for the server file descriptor is - * indeterminate - we may have done I/O on it in the oplock processing. JRA. - */ - goto again; - } - /* * Are there any timed events waiting ? If so, ensure we don't * select for longer than it would take to wait for them. @@ -826,20 +772,15 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, &r_fds, &w_fds, &to, &maxfd); } - if (timeval_is_zero(&to)) { - /* Process a timed event now... */ - if (run_events(smbd_event_context(), 0, NULL, NULL)) { - goto again; - } + /* Process a signal and timed events now... */ + if (run_events(smbd_event_context(), 0, NULL, NULL)) { + return NT_STATUS_RETRY; } - + { int sav; START_PROFILE(smbd_idle); - maxfd = select_on_fd(smbd_server_fd(), maxfd, &r_fds); - maxfd = select_on_fd(oplock_notify_fd(), maxfd, &r_fds); - selrtn = sys_select(maxfd+1,&r_fds,&w_fds,NULL,&to); sav = errno; @@ -848,21 +789,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, } if (run_events(smbd_event_context(), selrtn, &r_fds, &w_fds)) { - goto again; - } - - /* if we get EINTR then maybe we have received an oplock - signal - treat this as select returning 1. This is ugly, but - is the best we can do until the oplock code knows more about - signals */ - if (selrtn == -1 && errno == EINTR) { - async_processing(&r_fds); - /* - * After async processing we must go and do the select again, as - * the state of the flag in fds for the server file descriptor is - * indeterminate - we may have done I/O on it in the oplock processing. JRA. - */ - goto again; + return NT_STATUS_RETRY; } /* Check if error */ @@ -873,44 +800,11 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer, /* Did we timeout ? */ if (selrtn == 0) { - goto again; - } - - /* - * Ensure we process oplock break messages by preference. - * This is IMPORTANT ! Otherwise we can starve other processes - * sending us an oplock break message. JRA. - */ - - if (oplock_message_waiting(&r_fds)) { - async_processing(&r_fds); - /* - * After async processing we must go and do the select again, as - * the state of the flag in fds for the server file descriptor is - * indeterminate - we may have done I/O on it in the oplock processing. JRA. - */ - goto again; + return NT_STATUS_RETRY; } - /* - * We've just woken up from a protentially long select sleep. - * Ensure we process local messages as we need to synchronously - * process any messages from other smbd's to avoid file rename race - * conditions. This call is cheap if there are no messages waiting. - * JRA. - */ - message_dispatch(smbd_messaging_context()); - - status = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0, - p_unread, p_encrypted, &len); - - if (!NT_STATUS_IS_OK(status)) { - return status; - } - - *buffer_len = len; - - return NT_STATUS_OK; + /* should not be reached */ + return NT_STATUS_INTERNAL_ERROR; } /* @@ -936,31 +830,6 @@ NTSTATUS allow_new_trans(struct trans_state *list, int mid) return NT_STATUS_OK; } -/**************************************************************************** - We're terminating and have closed all our files/connections etc. - If there are any pending local messages we need to respond to them - before termination so that other smbds don't think we just died whilst - holding oplocks. -****************************************************************************/ - -void respond_to_all_remaining_local_messages(void) -{ - /* - * Assert we have no exclusive open oplocks. - */ - - if(get_number_of_exclusive_open_oplocks()) { - DEBUG(0,("respond_to_all_remaining_local_messages: PANIC : we have %d exclusive oplocks.\n", - get_number_of_exclusive_open_oplocks() )); - return; - } - - process_kernel_oplocks(smbd_messaging_context(), NULL); - - return; -} - - /* These flags determine some of the permissions required to do an operation @@ -1346,8 +1215,6 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in uint16 session_tag; connection_struct *conn = NULL; - static uint16 last_session_tag = UID_FIELD_INVALID; - errno = 0; /* Make sure this is an SMB packet. smb_size contains NetBIOS header @@ -1400,8 +1267,6 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in set_current_user_info( vuser->server_info->sanitized_username, vuser->server_info->unix_name, - pdb_get_fullname(vuser->server_info - ->sam_account), pdb_get_domain(vuser->server_info ->sam_account)); } @@ -1536,9 +1401,10 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool enc Process an smb from the client ****************************************************************************/ -static void process_smb(char *inbuf, size_t nread, size_t unread_bytes, bool encrypted) +static void process_smb(struct smbd_server_connection *conn, + uint8_t *inbuf, size_t nread, size_t unread_bytes, + bool encrypted) { - static int trans_num; int msg_type = CVAL(inbuf,0); DO_PROFILE_INC(smb_count); @@ -1553,15 +1419,31 @@ static void process_smb(char *inbuf, size_t nread, size_t unread_bytes, bool enc /* * NetBIOS session request, keepalive, etc. */ - reply_special(inbuf); - return; + reply_special((char *)inbuf); + goto done; } - show_msg(inbuf); + show_msg((char *)inbuf); - construct_reply(inbuf,nread,unread_bytes,encrypted); + construct_reply((char *)inbuf,nread,unread_bytes,encrypted); trans_num++; + +done: + conn->num_requests++; + + /* The timeout_processing function isn't run nearly + often enough to implement 'max log size' without + overrunning the size of the file by many megabytes. + This is especially true if we are running at debug + level 10. Checking every 50 SMBs is a nice + tradeoff of performance vs log file size overrun. */ + + if ((conn->num_requests % 50) == 0 && + need_to_check_log_size()) { + change_to_root_user(); + check_log_size(); + } } /**************************************************************************** @@ -1582,8 +1464,6 @@ const char *smb_fn_name(int type) Helper functions for contruct_reply. ****************************************************************************/ -static uint32 common_flags2 = FLAGS2_LONG_PATH_COMPONENTS|FLAGS2_32_BIT_ERROR_CODES; - void add_to_common_flags2(uint32 v) { common_flags2 |= v; @@ -1618,214 +1498,247 @@ void construct_reply_common_req(struct smb_request *req, char *outbuf) construct_reply_common(req, (char *)req->inbuf, outbuf); } +/* + * How many bytes have we already accumulated up to the current wct field + * offset? + */ + +size_t req_wct_ofs(struct smb_request *req) +{ + size_t buf_size; + + if (req->chain_outbuf == NULL) { + return smb_wct - 4; + } + buf_size = talloc_get_size(req->chain_outbuf); + if ((buf_size % 4) != 0) { + buf_size += (4 - (buf_size % 4)); + } + return buf_size - 4; +} + +/* + * Hack around reply_nterror & friends not being aware of chained requests, + * generating illegal (i.e. wct==0) chain replies. + */ + +static void fixup_chain_error_packet(struct smb_request *req) +{ + uint8_t *outbuf = req->outbuf; + req->outbuf = NULL; + reply_outbuf(req, 2, 0); + memcpy(req->outbuf, outbuf, smb_wct); + TALLOC_FREE(outbuf); + SCVAL(req->outbuf, smb_vwv0, 0xff); +} + /**************************************************************************** Construct a chained reply and add it to the already made reply ****************************************************************************/ void chain_reply(struct smb_request *req) { - static char *orig_inbuf; + size_t smblen = smb_len(req->inbuf); + size_t already_used, length_needed; + uint8_t chain_cmd; + uint32_t chain_offset; /* uint32_t to avoid overflow */ - /* - * Dirty little const_discard: We mess with req->inbuf, which is - * declared as const. If maybe at some point this routine gets - * rewritten, this const_discard could go away. - */ - char *inbuf = CONST_DISCARD(char *, req->inbuf); - int size = smb_len(req->inbuf)+4; - - int smb_com1, smb_com2 = CVAL(inbuf,smb_vwv0); - unsigned smb_off2 = SVAL(inbuf,smb_vwv1); - char *inbuf2; - int outsize2; - int new_size; - char inbuf_saved[smb_wct]; - char *outbuf = (char *)req->outbuf; - size_t outsize = smb_len(outbuf) + 4; - size_t outsize_padded; - size_t padding; - size_t ofs, to_move; - - struct smb_request *req2; - size_t caller_outputlen; - char *caller_output; - - /* Maybe its not chained, or it's an error packet. */ - if (smb_com2 == 0xFF || SVAL(outbuf,smb_rcls) != 0) { - SCVAL(outbuf,smb_vwv0,0xFF); - return; - } + uint8_t wct; + uint16_t *vwv; + uint16_t buflen; + uint8_t *buf; - if (chain_size == 0) { - /* this is the first part of the chain */ - orig_inbuf = inbuf; + if (IVAL(req->outbuf, smb_rcls) != 0) { + fixup_chain_error_packet(req); } /* - * We need to save the output the caller added to the chain so that we - * can splice it into the final output buffer later. + * Any of the AndX requests and replies have at least a wct of + * 2. vwv[0] is the next command, vwv[1] is the offset from the + * beginning of the SMB header to the next wct field. + * + * None of the AndX requests put anything valuable in vwv[0] and [1], + * so we can overwrite it here to form the chain. */ - caller_outputlen = outsize - smb_wct; - - caller_output = (char *)memdup(outbuf + smb_wct, caller_outputlen); - - if (caller_output == NULL) { - /* TODO: NT_STATUS_NO_MEMORY */ - smb_panic("could not dup outbuf"); + if ((req->wct < 2) || (CVAL(req->outbuf, smb_wct) < 2)) { + goto error; } /* - * The original Win95 redirector dies on a reply to - * a lockingX and read chain unless the chain reply is - * 4 byte aligned. JRA. + * Here we assume that this is the end of the chain. For that we need + * to set "next command" to 0xff and the offset to 0. If we later find + * more commands in the chain, this will be overwritten again. */ - outsize_padded = (outsize + 3) & ~3; - padding = outsize_padded - outsize; + SCVAL(req->outbuf, smb_vwv0, 0xff); + SCVAL(req->outbuf, smb_vwv0+1, 0); + SSVAL(req->outbuf, smb_vwv1, 0); - /* - * remember how much the caller added to the chain, only counting - * stuff after the parameter words - */ - chain_size += (outsize_padded - smb_wct); + if (req->chain_outbuf == NULL) { + /* + * In req->chain_outbuf we collect all the replies. Start the + * chain by copying in the first reply. + * + * We do the realloc because later on we depend on + * talloc_get_size to determine the length of + * chain_outbuf. The reply_xxx routines might have + * over-allocated (reply_pipe_read_and_X used to be such an + * example). + */ + req->chain_outbuf = TALLOC_REALLOC_ARRAY( + req, req->outbuf, uint8_t, smb_len(req->outbuf) + 4); + if (req->chain_outbuf == NULL) { + goto error; + } + req->outbuf = NULL; + } else { + if (!smb_splice_chain(&req->chain_outbuf, + CVAL(req->outbuf, smb_com), + CVAL(req->outbuf, smb_wct), + (uint16_t *)(req->outbuf + smb_vwv), + 0, smb_buflen(req->outbuf), + (uint8_t *)smb_buf(req->outbuf))) { + goto error; + } + TALLOC_FREE(req->outbuf); + } /* - * work out pointers into the original packets. The - * headers on these need to be filled in + * We use the old request's vwv field to grab the next chained command + * and offset into the chained fields. */ - inbuf2 = orig_inbuf + smb_off2 + 4 - smb_wct; - - /* remember the original command type */ - smb_com1 = CVAL(orig_inbuf,smb_com); - - /* save the data which will be overwritten by the new headers */ - memcpy(inbuf_saved,inbuf2,smb_wct); - - /* give the new packet the same header as the last part of the SMB */ - memmove(inbuf2,inbuf,smb_wct); - /* create the in buffer */ - SCVAL(inbuf2,smb_com,smb_com2); + chain_cmd = CVAL(req->vwv+0, 0); + chain_offset = SVAL(req->vwv+1, 0); - /* work out the new size for the in buffer. */ - new_size = size - (inbuf2 - inbuf); - if (new_size < 0) { - DEBUG(0,("chain_reply: chain packet size incorrect " - "(orig size = %d, offset = %d)\n", - size, (int)(inbuf2 - inbuf) )); - exit_server_cleanly("Bad chained packet"); + if (chain_cmd == 0xff) { + /* + * End of chain, no more requests from the client. So ship the + * replies. + */ + smb_setlen((char *)(req->chain_outbuf), + talloc_get_size(req->chain_outbuf) - 4); + if (!srv_send_smb(smbd_server_fd(), (char *)req->chain_outbuf, + IS_CONN_ENCRYPTED(req->conn) + ||req->encrypted)) { + exit_server_cleanly("chain_reply: srv_send_smb " + "failed."); + } return; } - /* And set it in the header. */ - smb_setlen(inbuf2, new_size - 4); - - DEBUG(3,("Chained message\n")); - show_msg(inbuf2); - - if (!(req2 = talloc(talloc_tos(), struct smb_request))) { - smb_panic("could not allocate smb_request"); - } - init_smb_request(req2, (uint8 *)inbuf2,0, req->encrypted); - req2->inbuf = (uint8_t *)inbuf2; - req2->chain_fsp = req->chain_fsp; - - /* process the request */ - switch_message(smb_com2, req2, new_size); - /* - * We don't accept deferred operations in chained requests. + * Check if the client tries to fool us. The request so far uses the + * space to the end of the byte buffer in the request just + * processed. The chain_offset can't point into that area. If that was + * the case, we could end up with an endless processing of the chain, + * we would always handle the same request. */ - SMB_ASSERT(req2->outbuf != NULL); - outsize2 = smb_len(req2->outbuf)+4; + + already_used = PTR_DIFF(req->buf+req->buflen, smb_base(req->inbuf)); + if (chain_offset < already_used) { + goto error; + } /* - * Move away the new command output so that caller_output fits in, - * copy in the caller_output saved above. + * Next check: Make sure the chain offset does not point beyond the + * overall smb request length. */ - SMB_ASSERT(outsize_padded >= smb_wct); + length_needed = chain_offset+1; /* wct */ + if (length_needed > smblen) { + goto error; + } /* - * "ofs" is the space we need for caller_output. Equal to - * caller_outputlen plus the padding. + * Now comes the pointer magic. Goal here is to set up req->vwv and + * req->buf correctly again to be able to call the subsequent + * switch_message(). The chain offset (the former vwv[1]) points at + * the new wct field. */ - ofs = outsize_padded - smb_wct; + wct = CVAL(smb_base(req->inbuf), chain_offset); /* - * "to_move" is the amount of bytes the secondary routine gave us + * Next consistency check: Make the new vwv array fits in the overall + * smb request. */ - to_move = outsize2 - smb_wct; - - if (to_move + ofs + smb_wct + chain_size > max_send) { - smb_panic("replies too large -- would have to cut"); + length_needed += (wct+1)*sizeof(uint16_t); /* vwv+buflen */ + if (length_needed > smblen) { + goto error; } + vwv = (uint16_t *)(smb_base(req->inbuf) + chain_offset + 1); /* - * In the "new" API "outbuf" is allocated via reply_outbuf, just for - * the first request in the chain. So we have to re-allocate it. In - * the "old" API the only outbuf ever used is the global OutBuffer - * which is always large enough. + * Now grab the new byte buffer.... */ - outbuf = TALLOC_REALLOC_ARRAY(NULL, outbuf, char, - to_move + ofs + smb_wct); - if (outbuf == NULL) { - smb_panic("could not realloc outbuf"); - } - - req->outbuf = (uint8 *)outbuf; - - memmove(outbuf + smb_wct + ofs, req2->outbuf + smb_wct, to_move); - memcpy(outbuf + smb_wct, caller_output, caller_outputlen); + buflen = SVAL(vwv+wct, 0); /* - * copy the new reply header over the old one but preserve the smb_com - * field + * .. and check that it fits. */ - memmove(outbuf, req2->outbuf, smb_wct); - SCVAL(outbuf, smb_com, smb_com1); - /* - * We've just copied in the whole "wct" area from the secondary - * function. Fix up the chaining: com2 and the offset need to be - * readjusted. - */ + length_needed += buflen; + if (length_needed > smblen) { + goto error; + } + buf = (uint8_t *)(vwv+wct+1); - SCVAL(outbuf, smb_vwv0, smb_com2); - SSVAL(outbuf, smb_vwv1, chain_size + smb_wct - 4); + req->cmd = chain_cmd; + req->wct = wct; + req->vwv = vwv; + req->buflen = buflen; + req->buf = buf; - if (padding != 0) { + switch_message(chain_cmd, req, smblen); + if (req->outbuf == NULL) { /* - * Due to padding we have some uninitialized bytes after the - * caller's output + * This happens if the chained command has suspended itself or + * if it has called srv_send_smb() itself. */ - - memset(outbuf + outsize, 0, padding); + return; } - smb_setlen(outbuf, outsize2 + caller_outputlen + padding - 4); - /* - * restore the saved data, being careful not to overwrite any data - * from the reply header + * We end up here if the chained command was not itself chained or + * suspended, but for example a close() command. We now need to splice + * the chained commands' outbuf into the already built up chain_outbuf + * and ship the result. */ - memcpy(inbuf2,inbuf_saved,smb_wct); - - SAFE_FREE(caller_output); - TALLOC_FREE(req2); + goto done; + error: /* - * Reset the chain_size for our caller's offset calculations + * We end up here if there's any error in the chain syntax. Report a + * DOS error, just like Windows does. */ + reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRerror)); + fixup_chain_error_packet(req); - chain_size -= (outsize_padded - smb_wct); + done: + if (!smb_splice_chain(&req->chain_outbuf, + CVAL(req->outbuf, smb_com), + CVAL(req->outbuf, smb_wct), + (uint16_t *)(req->outbuf + smb_vwv), + 0, smb_buflen(req->outbuf), + (uint8_t *)smb_buf(req->outbuf))) { + exit_server_cleanly("chain_reply: smb_splice_chain failed\n"); + } + TALLOC_FREE(req->outbuf); - return; + smb_setlen((char *)(req->chain_outbuf), + talloc_get_size(req->chain_outbuf) - 4); + + show_msg((char *)(req->chain_outbuf)); + + if (!srv_send_smb(smbd_server_fd(), (char *)req->chain_outbuf, + IS_CONN_ENCRYPTED(req->conn)||req->encrypted)) { + exit_server_cleanly("construct_reply: srv_send_smb failed."); + } } /**************************************************************************** @@ -1834,9 +1747,6 @@ void chain_reply(struct smb_request *req) void check_reload(time_t t) { - static pid_t mypid = 0; - static time_t last_smb_conf_reload_time = 0; - static time_t last_printer_reload_time = 0; time_t printcap_cache_time = (time_t)lp_printcap_cache_time(); if(last_smb_conf_reload_time == 0) { @@ -1859,9 +1769,8 @@ void check_reload(time_t t) mypid = getpid(); } - if (reload_after_sighup || (t >= last_smb_conf_reload_time+SMBD_RELOAD_CHECK)) { + if (t >= last_smb_conf_reload_time+SMBD_RELOAD_CHECK) { reload_services(True); - reload_after_sighup = False; last_smb_conf_reload_time = t; } @@ -1881,16 +1790,185 @@ void check_reload(time_t t) } } +static void smbd_server_connection_write_handler(struct smbd_server_connection *conn) +{ + /* TODO: make write nonblocking */ +} + +static void smbd_server_connection_read_handler(struct smbd_server_connection *conn) +{ + uint8_t *inbuf = NULL; + size_t inbuf_len = 0; + size_t unread_bytes = 0; + bool encrypted = false; + TALLOC_CTX *mem_ctx = talloc_tos(); + NTSTATUS status; + + /* TODO: make this completely nonblocking */ + + status = receive_smb_talloc(mem_ctx, smbd_server_fd(), + (char **)(void *)&inbuf, + 0, /* timeout */ + &unread_bytes, + &encrypted, + &inbuf_len); + if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) { + goto process; + } + if (NT_STATUS_IS_ERR(status)) { + exit_server_cleanly("failed to receive smb request"); + } + if (!NT_STATUS_IS_OK(status)) { + return; + } + +process: + process_smb(conn, inbuf, inbuf_len, unread_bytes, encrypted); +} + +static void smbd_server_connection_handler(struct event_context *ev, + struct fd_event *fde, + uint16_t flags, + void *private_data) +{ + struct smbd_server_connection *conn = talloc_get_type(private_data, + struct smbd_server_connection); + + if (flags & EVENT_FD_WRITE) { + smbd_server_connection_write_handler(conn); + } else if (flags & EVENT_FD_READ) { + smbd_server_connection_read_handler(conn); + } +} + + +/**************************************************************************** +received when we should release a specific IP +****************************************************************************/ +static void release_ip(const char *ip, void *priv) +{ + char addr[INET6_ADDRSTRLEN]; + + if (strcmp(client_socket_addr(get_client_fd(),addr,sizeof(addr)), ip) == 0) { + /* we can't afford to do a clean exit - that involves + database writes, which would potentially mean we + are still running after the failover has finished - + we have to get rid of this process ID straight + away */ + DEBUG(0,("Got release IP message for our IP %s - exiting immediately\n", + ip)); + /* note we must exit with non-zero status so the unclean handler gets + called in the parent, so that the brl database is tickled */ + _exit(1); + } +} + +static void msg_release_ip(struct messaging_context *msg_ctx, void *private_data, + uint32_t msg_type, struct server_id server_id, DATA_BLOB *data) +{ + release_ip((char *)data->data, NULL); +} + +#ifdef CLUSTER_SUPPORT +static int client_get_tcp_info(struct sockaddr_storage *server, + struct sockaddr_storage *client) +{ + socklen_t length; + if (server_fd == -1) { + return -1; + } + length = sizeof(*server); + if (getsockname(server_fd, (struct sockaddr *)server, &length) != 0) { + return -1; + } + length = sizeof(*client); + if (getpeername(server_fd, (struct sockaddr *)client, &length) != 0) { + return -1; + } + return 0; +} +#endif + +/* + * Send keepalive packets to our client + */ +static bool keepalive_fn(const struct timeval *now, void *private_data) +{ + if (!send_keepalive(smbd_server_fd())) { + DEBUG( 2, ( "Keepalive failed - exiting.\n" ) ); + return False; + } + return True; +} + +/* + * Do the recurring check if we're idle + */ +static bool deadtime_fn(const struct timeval *now, void *private_data) +{ + if ((conn_num_open() == 0) + || (conn_idle_all(now->tv_sec))) { + DEBUG( 2, ( "Closing idle connection\n" ) ); + messaging_send(smbd_messaging_context(), procid_self(), + MSG_SHUTDOWN, &data_blob_null); + return False; + } + + return True; +} + +/* + * Do the recurring log file and smb.conf reload checks. + */ + +static bool housekeeping_fn(const struct timeval *now, void *private_data) +{ + change_to_root_user(); + + /* update printer queue caches if necessary */ + update_monitored_printq_cache(); + + /* check if we need to reload services */ + check_reload(time(NULL)); + + /* Change machine password if neccessary. */ + attempt_machine_password_change(); + + /* + * Force a log file check. + */ + force_check_log_size(); + check_log_size(); + return true; +} + /**************************************************************************** Process commands from the client ****************************************************************************/ void smbd_process(void) { - unsigned int num_smbs = 0; - size_t unread_bytes = 0; + TALLOC_CTX *frame = talloc_stackframe(); + char remaddr[INET6_ADDRSTRLEN]; - char addr[INET6_ADDRSTRLEN]; + smbd_server_conn = talloc_zero(smbd_event_context(), struct smbd_server_connection); + if (!smbd_server_conn) { + exit_server("failed to create smbd_server_connection"); + } + + /* Ensure child is set to blocking mode */ + set_blocking(smbd_server_fd(),True); + + set_socket_options(smbd_server_fd(),"SO_KEEPALIVE"); + set_socket_options(smbd_server_fd(), lp_socket_options()); + + /* this is needed so that we get decent entries + in smbstatus for port 445 connects */ + set_remote_machine_name(get_peer_addr(smbd_server_fd(), + remaddr, + sizeof(remaddr)), + false); + reload_services(true); /* * Before the first packet, check the global hosts allow/ hosts deny @@ -1901,6 +1979,8 @@ void smbd_process(void) if (!check_access(smbd_server_fd(), lp_hostsallow(-1), lp_hostsdeny(-1))) { + char addr[INET6_ADDRSTRLEN]; + /* * send a negative session response "not listening on calling * name" @@ -1912,44 +1992,125 @@ void smbd_process(void) exit_server_cleanly("connection denied"); } - max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); + static_init_rpc; - while (True) { - NTSTATUS status; - char *inbuf = NULL; - size_t inbuf_len = 0; - bool encrypted = false; - TALLOC_CTX *frame = talloc_stackframe_pool(8192); + init_modules(); - errno = 0; + if (!init_account_policy()) { + exit_server("Could not open account policy tdb.\n"); + } + + if (*lp_rootdir()) { + if (chroot(lp_rootdir()) != 0) { + DEBUG(0,("Failed changed root to %s\n", lp_rootdir())); + exit_server("Failed to chroot()"); + } + DEBUG(0,("Changed root to %s\n", lp_rootdir())); + } - run_events(smbd_event_context(), 0, NULL, NULL); + /* Setup oplocks */ + if (!init_oplocks(smbd_messaging_context())) + exit_server("Failed to init oplocks"); - status = receive_message_or_smb( - talloc_tos(), &inbuf, &inbuf_len, - &unread_bytes, &encrypted); + /* Setup aio signal handler. */ + initialize_async_io_handler(); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(3, ("receive_message_or_smb failed: %s, " - "exiting\n", nt_errstr(status))); - return; + /* register our message handlers */ + messaging_register(smbd_messaging_context(), NULL, + MSG_SMB_FORCE_TDIS, msg_force_tdis); + messaging_register(smbd_messaging_context(), NULL, + MSG_SMB_RELEASE_IP, msg_release_ip); + messaging_register(smbd_messaging_context(), NULL, + MSG_SMB_CLOSE_FILE, msg_close_file); + + if ((lp_keepalive() != 0) + && !(event_add_idle(smbd_event_context(), NULL, + timeval_set(lp_keepalive(), 0), + "keepalive", keepalive_fn, + NULL))) { + DEBUG(0, ("Could not add keepalive event\n")); + exit(1); + } + + if (!(event_add_idle(smbd_event_context(), NULL, + timeval_set(IDLE_CLOSED_TIMEOUT, 0), + "deadtime", deadtime_fn, NULL))) { + DEBUG(0, ("Could not add deadtime event\n")); + exit(1); + } + + if (!(event_add_idle(smbd_event_context(), NULL, + timeval_set(SMBD_SELECT_TIMEOUT, 0), + "housekeeping", housekeeping_fn, NULL))) { + DEBUG(0, ("Could not add housekeeping event\n")); + exit(1); + } + +#ifdef CLUSTER_SUPPORT + + if (lp_clustering()) { + /* + * We need to tell ctdb about our client's TCP + * connection, so that for failover ctdbd can send + * tickle acks, triggering a reconnection by the + * client. + */ + + struct sockaddr_storage srv, clnt; + + if (client_get_tcp_info(&srv, &clnt) == 0) { + + NTSTATUS status; + + status = ctdbd_register_ips( + messaging_ctdbd_connection(), + &srv, &clnt, release_ip, NULL); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("ctdbd_register_ips failed: %s\n", + nt_errstr(status))); + } + } else + { + DEBUG(0,("Unable to get tcp info for " + "CTDB_CONTROL_TCP_CLIENT: %s\n", + strerror(errno))); } + } - process_smb(inbuf, inbuf_len, unread_bytes, encrypted); +#endif - num_smbs++; + max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); - /* The timeout_processing function isn't run nearly - often enough to implement 'max log size' without - overrunning the size of the file by many megabytes. - This is especially true if we are running at debug - level 10. Checking every 50 SMBs is a nice - tradeoff of performance vs log file size overrun. */ + smbd_server_conn->fde = event_add_fd(smbd_event_context(), + smbd_server_conn, + smbd_server_fd(), + EVENT_FD_READ, + smbd_server_connection_handler, + smbd_server_conn); + if (!smbd_server_conn->fde) { + exit_server("failed to create smbd_server_connection fde"); + } - if ((num_smbs % 50) == 0 && need_to_check_log_size()) { - change_to_root_user(); - check_log_size(); + TALLOC_FREE(frame); + + while (True) { + NTSTATUS status; + + frame = talloc_stackframe_pool(8192); + + errno = 0; + + status = smbd_server_connection_loop_once(smbd_server_conn); + if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY) && + !NT_STATUS_IS_OK(status)) { + DEBUG(3, ("smbd_server_connection_loop_once failed: %s," + " exiting\n", nt_errstr(status))); + break; } + TALLOC_FREE(frame); } + + exit_server_cleanly(NULL); }