X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Fsmbd%2Fprocess.c;h=16ed0f60b701e688b1739986991daa6e55d39126;hb=c4789521d5e724e45c5881f74d55daf7ac59eb40;hp=0c076b3a53555110e0c1bebdc630136d52b574d6;hpb=0d7ca8e89e37d1aa07a4c8fad6a24ac41ceb4855;p=obnox%2Fsamba-ctdb.git diff --git a/source3/smbd/process.c b/source3/smbd/process.c index 0c076b3a53..16ed0f60b7 100644 --- a/source3/smbd/process.c +++ b/source3/smbd/process.c @@ -26,21 +26,80 @@ extern bool global_machine_password_needs_changing; static void construct_reply_common(struct smb_request *req, const char *inbuf, char *outbuf); +bool smbd_lock_socket(struct smbd_server_connection *sconn) +{ + bool ok; + + if (smbd_server_conn->smb1.echo_handler.socket_lock_fd == -1) { + return true; + } + + DEBUG(10,("pid[%d] wait for socket lock\n", (int)sys_getpid())); + + do { + ok = fcntl_lock( + smbd_server_conn->smb1.echo_handler.socket_lock_fd, + SMB_F_SETLKW, 0, 0, F_WRLCK); + } while (!ok && (errno == EINTR)); + + if (!ok) { + return false; + } + + DEBUG(10,("pid[%d] got for socket lock\n", (int)sys_getpid())); + + return true; +} + +bool smbd_unlock_socket(struct smbd_server_connection *sconn) +{ + bool ok; + + if (smbd_server_conn->smb1.echo_handler.socket_lock_fd == -1) { + return true; + } + + do { + ok = fcntl_lock( + smbd_server_conn->smb1.echo_handler.socket_lock_fd, + SMB_F_SETLKW, 0, 0, F_UNLCK); + } while (!ok && (errno == EINTR)); + + if (!ok) { + return false; + } + + DEBUG(10,("pid[%d] unlocked socket\n", (int)sys_getpid())); + + return true; +} + /* Accessor function for smb_read_error for smbd functions. */ /**************************************************************************** Send an smb to a fd. ****************************************************************************/ -bool srv_send_smb(int fd, char *buffer, bool do_encrypt) +bool srv_send_smb(int fd, char *buffer, + bool do_signing, uint32_t seqnum, + bool do_encrypt, + struct smb_perfcount_data *pcd) { - size_t len; + size_t len = 0; size_t nwritten=0; ssize_t ret; char *buf_out = buffer; + bool ok; - /* Sign the outgoing packet if required. */ - srv_calculate_sign_mac(buf_out); + ok = smbd_lock_socket(smbd_server_conn); + if (!ok) { + exit_server_cleanly("failed to lock socket"); + } + + if (do_signing) { + /* Sign the outgoing packet if required. */ + srv_calculate_sign_mac(smbd_server_conn, buf_out, seqnum); + } if (do_encrypt) { NTSTATUS status = srv_encrypt_buffer(buffer, &buf_out); @@ -48,7 +107,7 @@ bool srv_send_smb(int fd, char *buffer, bool do_encrypt) DEBUG(0, ("send_smb: SMB encryption failed " "on outgoing packet! Error %s\n", nt_errstr(status) )); - return false; + goto out; } } @@ -57,15 +116,24 @@ bool srv_send_smb(int fd, char *buffer, bool do_encrypt) while (nwritten < len) { ret = write_data(fd,buf_out+nwritten,len - nwritten); if (ret <= 0) { - DEBUG(0,("Error writing %d bytes to client. %d. (%s)\n", - (int)len,(int)ret, strerror(errno) )); + DEBUG(0,("pid[%d] Error writing %d bytes to client. %d. (%s)\n", + (int)sys_getpid(), (int)len,(int)ret, strerror(errno) )); srv_free_enc_buffer(buf_out); - return false; + goto out; } nwritten += ret; } + SMB_PERFCOUNT_SET_MSGLEN_OUT(pcd, len); srv_free_enc_buffer(buf_out); +out: + SMB_PERFCOUNT_END(pcd); + + ok = smbd_unlock_socket(smbd_server_conn); + if (!ok) { + exit_server_cleanly("failed to unlock socket"); + } + return true; } @@ -123,7 +191,7 @@ static NTSTATUS read_packet_remainder(int fd, char *buffer, return NT_STATUS_OK; } - return read_socket_with_timeout(fd, buffer, len, len, timeout, NULL); + return read_fd_with_timeout(fd, buffer, len, len, timeout, NULL); } /**************************************************************************** @@ -157,7 +225,7 @@ static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx, memcpy(writeX_header, lenbuf, 4); - status = read_socket_with_timeout( + status = read_fd_with_timeout( fd, writeX_header + 4, STANDARD_WRITE_AND_X_HEADER_SIZE, STANDARD_WRITE_AND_X_HEADER_SIZE, @@ -268,10 +336,11 @@ static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd, return status; } - if (CVAL(lenbuf,0) == 0 && - min_recv_size && - smb_len_large(lenbuf) > (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE) && /* Could be a UNIX large writeX. */ - !srv_is_signing_active()) { + if (CVAL(lenbuf,0) == 0 && min_recv_size && + (smb_len_large(lenbuf) > /* Could be a UNIX large writeX. */ + (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE)) && + !srv_is_signing_active(smbd_server_conn) && + smbd_server_conn->smb1.echo_handler.trusted_fde == NULL) { return receive_smb_raw_talloc_partial_read( mem_ctx, lenbuf, fd, buffer, timeout, p_unread, plen); @@ -307,7 +376,9 @@ static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd, static NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, char **buffer, unsigned int timeout, size_t *p_unread, bool *p_encrypted, - size_t *p_len) + size_t *p_len, + uint32_t *seqnum, + bool trusted_channel) { size_t len = 0; NTSTATUS status; @@ -332,7 +403,7 @@ static NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx, int fd, } /* Check the incoming SMB signature. */ - if (!srv_check_sign_mac(*buffer, true)) { + if (!srv_check_sign_mac(smbd_server_conn, *buffer, seqnum, trusted_channel)) { DEBUG(0, ("receive_smb: SMB Signature verification failed on " "incoming packet!\n")); return NT_STATUS_INVALID_NETWORK_RESPONSE; @@ -362,6 +433,7 @@ void init_smb_request(struct smb_request *req, req->flags2 = SVAL(inbuf, smb_flg2); req->smbpid = SVAL(inbuf, smb_pid); req->mid = SVAL(inbuf, smb_mid); + req->seqnum = 0; req->vuid = SVAL(inbuf, smb_uid); req->tid = SVAL(inbuf, smb_tid); req->wct = CVAL(inbuf, smb_wct); @@ -373,6 +445,8 @@ void init_smb_request(struct smb_request *req, req->conn = conn_find(req->tid); req->chain_fsp = NULL; req->chain_outbuf = NULL; + req->done = false; + smb_init_perfcount_data(&req->pcd); /* Ensure we have at least wct words and 2 bytes of bcc. */ if (smb_size + req->wct*2 > req_size) { @@ -390,12 +464,14 @@ void init_smb_request(struct smb_request *req, (unsigned int)req_size)); exit_server_cleanly("Invalid SMB request"); } + req->outbuf = NULL; } static void process_smb(struct smbd_server_connection *conn, uint8_t *inbuf, size_t nread, size_t unread_bytes, - bool encrypted); + uint32_t seqnum, bool encrypted, + struct smb_perfcount_data *deferred_pcd); static void smbd_deferred_open_timer(struct event_context *ev, struct timed_event *te, @@ -405,6 +481,7 @@ static void smbd_deferred_open_timer(struct event_context *ev, struct pending_message_list *msg = talloc_get_type(private_data, struct pending_message_list); TALLOC_CTX *mem_ctx = talloc_tos(); + uint16_t mid = SVAL(msg->buf.data,smb_mid); uint8_t *inbuf; inbuf = (uint8_t *)talloc_memdup(mem_ctx, msg->buf.data, @@ -417,11 +494,21 @@ static void smbd_deferred_open_timer(struct event_context *ev, /* 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))); + (unsigned int)mid)); + + /* Mark the message as processed so this is not + * re-processed in error. */ + msg->processed = true; process_smb(smbd_server_conn, inbuf, msg->buf.length, 0, - msg->encrypted); + msg->seqnum, msg->encrypted, &msg->pcd); + + /* If it's still there and was processed, remove it. */ + msg = get_open_deferred_message(mid); + if (msg && msg->processed) { + remove_deferred_open_smb_message(mid); + } } /**************************************************************************** @@ -452,7 +539,10 @@ static bool push_queued_message(struct smb_request *req, } msg->request_time = request_time; + msg->seqnum = req->seqnum; msg->encrypted = req->encrypted; + msg->processed = false; + SMB_PERFCOUNT_DEFER_OP(&req->pcd, &msg->pcd); if (private_data) { msg->private_data = data_blob_talloc(msg, private_data, @@ -493,7 +583,7 @@ void remove_deferred_open_smb_message(uint16 mid) for (pml = deferred_open_queue; pml; pml = pml->next) { if (mid == SVAL(pml->buf.data,smb_mid)) { - DEBUG(10,("remove_sharing_violation_open_smb_message: " + DEBUG(10,("remove_deferred_open_smb_message: " "deleting mid %u len %u\n", (unsigned int)mid, (unsigned int)pml->buf.length )); @@ -523,6 +613,15 @@ void schedule_deferred_open_smb_message(uint16 mid) if (mid == msg_mid) { struct timed_event *te; + if (pml->processed) { + /* A processed message should not be + * rescheduled. */ + DEBUG(0,("schedule_deferred_open_smb_message: LOGIC ERROR " + "message mid %u was already processed\n", + msg_mid )); + continue; + } + DEBUG(10,("schedule_deferred_open_smb_message: scheduling mid %u\n", mid )); @@ -549,7 +648,7 @@ void schedule_deferred_open_smb_message(uint16 mid) } /**************************************************************************** - Return true if this mid is on the deferred queue. + Return true if this mid is on the deferred queue and was not yet processed. ****************************************************************************/ bool open_was_deferred(uint16 mid) @@ -557,7 +656,7 @@ bool open_was_deferred(uint16 mid) struct pending_message_list *pml; for (pml = deferred_open_queue; pml; pml = pml->next) { - if (SVAL(pml->buf.data,smb_mid) == mid) { + if (SVAL(pml->buf.data,smb_mid) == mid && !pml->processed) { return True; } } @@ -692,71 +791,55 @@ struct idle_event *event_add_idle(struct event_context *event_ctx, 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 smbd_server_connection_loop_once(struct smbd_server_connection *conn) { @@ -768,13 +851,6 @@ static NTSTATUS smbd_server_connection_loop_once(struct smbd_server_connection * 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()); - /* * Setup the select fd sets. */ @@ -782,26 +858,6 @@ static NTSTATUS smbd_server_connection_loop_once(struct smbd_server_connection * 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. - */ - return NT_STATUS_RETRY; - } - /* * Are there any timed events waiting ? If so, ensure we don't * select for longer than it would take to wait for them. @@ -815,19 +871,15 @@ static NTSTATUS smbd_server_connection_loop_once(struct smbd_server_connection * &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)) { - return NT_STATUS_RETRY; - } + /* 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(oplock_notify_fd(), maxfd, &r_fds); - selrtn = sys_select(maxfd+1,&r_fds,&w_fds,NULL,&to); sav = errno; @@ -835,21 +887,18 @@ static NTSTATUS smbd_server_connection_loop_once(struct smbd_server_connection * errno = sav; } - if (run_events(smbd_event_context(), selrtn, &r_fds, &w_fds)) { - return NT_STATUS_RETRY; - } - - /* 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); + if ((conn->smb1.echo_handler.trusted_fd != -1) + && FD_ISSET(smbd_server_fd(), &r_fds) + && FD_ISSET(conn->smb1.echo_handler.trusted_fd, &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. + * Prefer to read pending requests from the echo handler. To + * quote Jeremy (da70f8ab1): This is a hack of monstrous + * proportions... */ + FD_CLR(smbd_server_fd(), &r_fds); + } + + if (run_events(smbd_event_context(), selrtn, &r_fds, &w_fds)) { return NT_STATUS_RETRY; } @@ -864,32 +913,8 @@ static NTSTATUS smbd_server_connection_loop_once(struct smbd_server_connection * return NT_STATUS_RETRY; } - /* - * 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. - */ - 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()); - - return NT_STATUS_OK; + /* should not be reached */ + return NT_STATUS_INTERNAL_ERROR; } /* @@ -915,31 +940,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 @@ -1016,7 +1016,7 @@ static const struct smb_message_struct { /* 0x30 */ { NULL, NULL, 0 }, /* 0x31 */ { NULL, NULL, 0 }, /* 0x32 */ { "SMBtrans2",reply_trans2, AS_USER | CAN_IPC }, -/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER}, +/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER | CAN_IPC }, /* 0x34 */ { "SMBfindclose",reply_findclose,AS_USER}, /* 0x35 */ { "SMBfindnclose",reply_findnclose,AS_USER}, /* 0x36 */ { NULL, NULL, 0 }, @@ -1377,8 +1377,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)); } @@ -1404,7 +1402,6 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in if (!change_to_user(conn,session_tag)) { reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRbaduid)); - remove_deferred_open_smb_message(req->mid); return conn; } @@ -1466,18 +1463,29 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in Construct a reply to the incoming packet. ****************************************************************************/ -static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool encrypted) +static void construct_reply(char *inbuf, int size, size_t unread_bytes, + uint32_t seqnum, bool encrypted, + struct smb_perfcount_data *deferred_pcd) { connection_struct *conn; struct smb_request *req; - chain_size = 0; - if (!(req = talloc(talloc_tos(), struct smb_request))) { smb_panic("could not allocate smb_request"); } + init_smb_request(req, (uint8 *)inbuf, unread_bytes, encrypted); req->inbuf = (uint8_t *)talloc_move(req, &inbuf); + req->seqnum = seqnum; + + /* we popped this message off the queue - keep original perf data */ + if (deferred_pcd) + req->pcd = *deferred_pcd; + else { + SMB_PERFCOUNT_START(&req->pcd); + SMB_PERFCOUNT_SET_OP(&req->pcd, req->cmd); + SMB_PERFCOUNT_SET_MSGLEN_IN(&req->pcd, size); + } conn = switch_message(req->cmd, req, size); @@ -1490,6 +1498,11 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool enc req->unread_bytes = 0; } + if (req->done) { + TALLOC_FREE(req); + return; + } + if (req->outbuf == NULL) { return; } @@ -1500,7 +1513,9 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool enc if (!srv_send_smb(smbd_server_fd(), (char *)req->outbuf, - IS_CONN_ENCRYPTED(conn)||req->encrypted)) { + true, req->seqnum+1, + IS_CONN_ENCRYPTED(conn)||req->encrypted, + &req->pcd)) { exit_server_cleanly("construct_reply: srv_send_smb failed."); } @@ -1512,10 +1527,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(struct smbd_server_connection *conn, uint8_t *inbuf, size_t nread, size_t unread_bytes, - bool encrypted) + uint32_t seqnum, bool encrypted, + struct smb_perfcount_data *deferred_pcd) { int msg_type = CVAL(inbuf,0); @@ -1537,8 +1552,7 @@ static void process_smb(struct smbd_server_connection *conn, show_msg((char *)inbuf); - construct_reply((char *)inbuf,nread,unread_bytes,encrypted); - + construct_reply((char *)inbuf,nread,unread_bytes,seqnum,encrypted,deferred_pcd); trans_num++; done: @@ -1691,10 +1705,27 @@ void chain_reply(struct smb_request *req) /* * 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 = req->outbuf; + 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 { + /* + * Update smb headers where subsequent chained commands + * may have updated them. + */ + SCVAL(req->chain_outbuf, smb_tid, CVAL(req->outbuf, smb_tid)); + SCVAL(req->chain_outbuf, smb_uid, CVAL(req->outbuf, smb_uid)); + if (!smb_splice_chain(&req->chain_outbuf, CVAL(req->outbuf, smb_com), CVAL(req->outbuf, smb_wct), @@ -1721,15 +1752,25 @@ void chain_reply(struct smb_request *req) */ smb_setlen((char *)(req->chain_outbuf), talloc_get_size(req->chain_outbuf) - 4); + if (!srv_send_smb(smbd_server_fd(), (char *)req->chain_outbuf, + true, req->seqnum+1, IS_CONN_ENCRYPTED(req->conn) - ||req->encrypted)) { + ||req->encrypted, + &req->pcd)) { exit_server_cleanly("chain_reply: srv_send_smb " "failed."); } + TALLOC_FREE(req->chain_outbuf); + req->done = true; return; } + /* add a new perfcounter for this element of chain */ + SMB_PERFCOUNT_ADD(&req->pcd); + SMB_PERFCOUNT_SET_OP(&req->pcd, chain_cmd); + SMB_PERFCOUNT_SET_MSGLEN_IN(&req->pcd, smblen); + /* * 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 @@ -1838,9 +1879,13 @@ void chain_reply(struct smb_request *req) show_msg((char *)(req->chain_outbuf)); if (!srv_send_smb(smbd_server_fd(), (char *)req->chain_outbuf, - IS_CONN_ENCRYPTED(req->conn)||req->encrypted)) { + true, req->seqnum+1, + IS_CONN_ENCRYPTED(req->conn)||req->encrypted, + &req->pcd)) { exit_server_cleanly("construct_reply: srv_send_smb failed."); } + TALLOC_FREE(req->chain_outbuf); + req->done = true; } /**************************************************************************** @@ -1871,9 +1916,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; } @@ -1893,12 +1937,29 @@ void check_reload(time_t t) } } +static bool fd_is_readable(int fd) +{ + fd_set fds; + struct timeval timeout = {0, }; + int ret; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + ret = sys_select(fd+1, &fds, NULL, NULL, &timeout); + if (ret == -1) { + return false; + } + return FD_ISSET(fd, &fds); +} + 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) +static void smbd_server_connection_read_handler( + struct smbd_server_connection *conn, int fd) { uint8_t *inbuf = NULL; size_t inbuf_len = 0; @@ -1906,15 +1967,50 @@ static void smbd_server_connection_read_handler(struct smbd_server_connection *c bool encrypted = false; TALLOC_CTX *mem_ctx = talloc_tos(); NTSTATUS status; + uint32_t seqnum; - /* TODO: make this completely nonblocking */ + bool ok; + + bool from_client = (smbd_server_fd() == fd)?true:false; + + if (from_client) { + ok = smbd_lock_socket(conn); + if (!ok) { + exit_server_cleanly("failed to lock socket"); + } + + if (!fd_is_readable(smbd_server_fd())) { + DEBUG(10,("the echo listener was faster\n")); + ok = smbd_unlock_socket(conn); + if (!ok) { + exit_server_cleanly("failed to unlock"); + } + return; + } + + /* TODO: make this completely nonblocking */ + status = receive_smb_talloc(mem_ctx, fd, + (char **)(void *)&inbuf, + 0, /* timeout */ + &unread_bytes, + &encrypted, + &inbuf_len, &seqnum, + false /* trusted channel */); + ok = smbd_unlock_socket(conn); + if (!ok) { + exit_server_cleanly("failed to unlock"); + } + } else { + /* TODO: make this completely nonblocking */ + status = receive_smb_talloc(mem_ctx, fd, + (char **)(void *)&inbuf, + 0, /* timeout */ + &unread_bytes, + &encrypted, + &inbuf_len, &seqnum, + true /* trusted channel */); + } - 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; } @@ -1926,7 +2022,8 @@ static void smbd_server_connection_read_handler(struct smbd_server_connection *c } process: - process_smb(conn, inbuf, inbuf_len, unread_bytes, encrypted); + process_smb(conn, inbuf, inbuf_len, unread_bytes, + seqnum, encrypted, NULL); } static void smbd_server_connection_handler(struct event_context *ev, @@ -1940,16 +2037,565 @@ static void smbd_server_connection_handler(struct event_context *ev, if (flags & EVENT_FD_WRITE) { smbd_server_connection_write_handler(conn); } else if (flags & EVENT_FD_READ) { - smbd_server_connection_read_handler(conn); + smbd_server_connection_read_handler(conn, smbd_server_fd()); + } +} + +static void smbd_server_echo_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, conn->smb1.echo_handler.trusted_fd); } } +/**************************************************************************** +received when we should release a specific IP +****************************************************************************/ +static void release_ip(const char *ip, void *priv) +{ + char addr[INET6_ADDRSTRLEN]; + char *p = addr; + + client_socket_addr(get_client_fd(),addr,sizeof(addr)); + + if (strncmp("::ffff:", addr, 7) == 0) { + p = addr + 7; + } + + if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(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) +{ + bool ok; + bool ret; + + ok = smbd_lock_socket(smbd_server_conn); + if (!ok) { + exit_server_cleanly("failed to lock socket"); + } + + ret = send_keepalive(smbd_server_fd()); + + ok = smbd_unlock_socket(smbd_server_conn); + if (!ok) { + exit_server_cleanly("failed to unlock socket"); + } + + if (!ret) { + 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; +} + +static int create_unlink_tmp(const char *dir) +{ + char *fname; + int fd; + + fname = talloc_asprintf(talloc_tos(), "%s/listenerlock_XXXXXX", dir); + if (fname == NULL) { + errno = ENOMEM; + return -1; + } + fd = mkstemp(fname); + if (fd == -1) { + TALLOC_FREE(fname); + return -1; + } + if (unlink(fname) == -1) { + int sys_errno = errno; + close(fd); + TALLOC_FREE(fname); + errno = sys_errno; + return -1; + } + TALLOC_FREE(fname); + return fd; +} + +struct smbd_echo_state { + struct tevent_context *ev; + struct iovec *pending; + struct smbd_server_connection *sconn; + int parent_pipe; + + struct tevent_fd *parent_fde; + + struct tevent_fd *read_fde; + struct tevent_req *write_req; +}; + +static void smbd_echo_writer_done(struct tevent_req *req); + +static void smbd_echo_activate_writer(struct smbd_echo_state *state) +{ + int num_pending; + + if (state->write_req != NULL) { + return; + } + + num_pending = talloc_array_length(state->pending); + if (num_pending == 0) { + return; + } + + state->write_req = writev_send(state, state->ev, NULL, + state->parent_pipe, + state->pending, num_pending); + if (state->write_req == NULL) { + DEBUG(1, ("writev_send failed\n")); + exit(1); + } + + talloc_steal(state->write_req, state->pending); + state->pending = NULL; + + tevent_req_set_callback(state->write_req, smbd_echo_writer_done, + state); +} + +static void smbd_echo_writer_done(struct tevent_req *req) +{ + struct smbd_echo_state *state = tevent_req_callback_data( + req, struct smbd_echo_state); + ssize_t written; + int err; + + written = writev_recv(req, &err); + TALLOC_FREE(req); + state->write_req = NULL; + if (written == -1) { + DEBUG(1, ("writev to parent failed: %s\n", strerror(err))); + exit(1); + } + DEBUG(10,("echo_handler[%d]: forwarded pdu to main\n", (int)sys_getpid())); + smbd_echo_activate_writer(state); +} + +static bool smbd_echo_reply(int fd, + uint8_t *inbuf, size_t inbuf_len, + uint32_t seqnum) +{ + struct smb_request req; + uint16_t num_replies; + size_t out_len; + char *outbuf; + bool ok; + + if ((inbuf_len == 4) && (CVAL(inbuf, 0) == SMBkeepalive)) { + DEBUG(10, ("Got netbios keepalive\n")); + /* + * Just swallow it + */ + return true; + } + + if (inbuf_len < smb_size) { + DEBUG(10, ("Got short packet: %d bytes\n", (int)inbuf_len)); + return false; + } + if (!valid_smb_header(inbuf)) { + DEBUG(10, ("Got invalid SMB header\n")); + return false; + } + + init_smb_request(&req, inbuf, 0, false); + req.inbuf = inbuf; + req.seqnum = seqnum; + + DEBUG(10, ("smbecho handler got cmd %d (%s)\n", (int)req.cmd, + smb_messages[req.cmd].name + ? smb_messages[req.cmd].name : "unknown")); + + if (req.cmd != SMBecho) { + return false; + } + if (req.wct < 1) { + return false; + } + + num_replies = SVAL(req.vwv+0, 0); + if (num_replies != 1) { + /* Not a Windows "Hey, you're still there?" request */ + return false; + } + + if (!create_outbuf(talloc_tos(), &req, (char *)req.inbuf, &outbuf, + 1, req.buflen)) { + DEBUG(10, ("create_outbuf failed\n")); + return false; + } + req.outbuf = (uint8_t *)outbuf; + + SSVAL(req.outbuf, smb_vwv0, num_replies); + + if (req.buflen > 0) { + memcpy(smb_buf(req.outbuf), req.buf, req.buflen); + } + + out_len = smb_len(req.outbuf) + 4; + + ok = srv_send_smb(smbd_server_fd(), + (char *)outbuf, + true, seqnum+1, + false, &req.pcd); + TALLOC_FREE(outbuf); + if (!ok) { + exit(1); + } + + return true; +} + +static void smbd_echo_exit(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *private_data) +{ + DEBUG(2, ("smbd_echo_exit: lost connection to parent\n")); + exit(0); +} + +static void smbd_echo_reader(struct tevent_context *ev, + struct tevent_fd *fde, uint16_t flags, + void *private_data) +{ + struct smbd_echo_state *state = talloc_get_type_abort( + private_data, struct smbd_echo_state); + struct smbd_server_connection *sconn = state->sconn; + size_t unread, num_pending; + NTSTATUS status; + struct iovec *tmp; + uint32_t seqnum = 0; + bool reply; + bool ok; + bool encrypted = false; + + smb_msleep(1000); + + ok = smbd_lock_socket(sconn); + if (!ok) { + DEBUG(0, ("%s: failed to lock socket\n", + __location__)); + exit(1); + } + + if (!fd_is_readable(smbd_server_fd())) { + DEBUG(10,("echo_handler[%d] the parent smbd was faster\n", + (int)sys_getpid())); + ok = smbd_unlock_socket(sconn); + if (!ok) { + DEBUG(1, ("%s: failed to unlock socket in\n", + __location__)); + exit(1); + } + return; + } + + num_pending = talloc_array_length(state->pending); + tmp = talloc_realloc(state, state->pending, struct iovec, + num_pending+1); + if (tmp == NULL) { + DEBUG(1, ("talloc_realloc failed\n")); + exit(1); + } + state->pending = tmp; + + DEBUG(10,("echo_handler[%d]: reading pdu\n", (int)sys_getpid())); + + status = receive_smb_talloc(state->pending, smbd_server_fd(), + (char **)(void *)&state->pending[num_pending].iov_base, + 0 /* timeout */, + &unread, + &encrypted, + &state->pending[num_pending].iov_len, + &seqnum, + false /* trusted_channel*/); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("echo_handler[%d]: receive_smb_raw_talloc failed: %s\n", + (int)sys_getpid(), nt_errstr(status))); + exit(1); + } + + ok = smbd_unlock_socket(sconn); + if (!ok) { + DEBUG(1, ("%s: failed to unlock socket in\n", + __location__)); + exit(1); + } + + reply = smbd_echo_reply(smbd_server_fd(), + (uint8_t *)state->pending[num_pending].iov_base, + state->pending[num_pending].iov_len, + seqnum); + if (reply) { + DEBUG(10,("echo_handler[%d]: replied to client\n", (int)sys_getpid())); + /* no check, shrinking by some bytes does not fail */ + state->pending = talloc_realloc(state, state->pending, + struct iovec, + num_pending); + return; + } + + if (state->pending[num_pending].iov_len >= smb_size) { + /* + * place the seqnum in the packet so that the main process + * can reply with signing + */ + SIVAL((uint8_t *)state->pending[num_pending].iov_base, + smb_ss_field, seqnum); + SIVAL((uint8_t *)state->pending[num_pending].iov_base, + smb_ss_field+4, NT_STATUS_V(NT_STATUS_OK)); + } + + DEBUG(10,("echo_handler[%d]: forward to main\n", (int)sys_getpid())); + smbd_echo_activate_writer(state); +} + +static void smbd_echo_loop(struct smbd_server_connection *sconn, + int parent_pipe) +{ + struct smbd_echo_state *state; + + state = talloc_zero(sconn, struct smbd_echo_state); + if (state == NULL) { + DEBUG(1, ("talloc failed\n")); + return; + } + state->sconn = sconn; + state->parent_pipe = parent_pipe; + state->ev = s3_tevent_context_init(state); + if (state->ev == NULL) { + DEBUG(1, ("tevent_context_init failed\n")); + TALLOC_FREE(state); + return; + } + state->parent_fde = tevent_add_fd(state->ev, state, parent_pipe, + TEVENT_FD_READ, smbd_echo_exit, + state); + if (state->parent_fde == NULL) { + DEBUG(1, ("tevent_add_fd failed\n")); + TALLOC_FREE(state); + return; + } + state->read_fde = tevent_add_fd(state->ev, state, smbd_server_fd(), + TEVENT_FD_READ, smbd_echo_reader, + state); + if (state->read_fde == NULL) { + DEBUG(1, ("tevent_add_fd failed\n")); + TALLOC_FREE(state); + return; + } + + while (true) { + if (tevent_loop_once(state->ev) == -1) { + DEBUG(1, ("tevent_loop_once failed: %s\n", + strerror(errno))); + break; + } + } + TALLOC_FREE(state); +} + +/* + * Handle SMBecho requests in a forked child process + */ +static bool fork_echo_handler(struct smbd_server_connection *sconn) +{ + int listener_pipe[2]; + int res; + pid_t child; + + res = pipe(listener_pipe); + if (res == -1) { + DEBUG(1, ("pipe() failed: %s\n", strerror(errno))); + return false; + } + sconn->smb1.echo_handler.socket_lock_fd = create_unlink_tmp(lp_lockdir()); + if (sconn->smb1.echo_handler.socket_lock_fd == -1) { + DEBUG(1, ("Could not create lock fd: %s\n", strerror(errno))); + goto fail; + } + + child = sys_fork(); + if (child == 0) { + NTSTATUS status; + + close(listener_pipe[0]); + set_blocking(listener_pipe[1], false); + + status = reinit_after_fork(smbd_messaging_context(), + smbd_event_context(), false); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("reinit_after_fork failed: %s\n", + nt_errstr(status))); + exit(1); + } + smbd_echo_loop(sconn, listener_pipe[1]); + exit(0); + } + close(listener_pipe[1]); + listener_pipe[1] = -1; + sconn->smb1.echo_handler.trusted_fd = listener_pipe[0]; + + DEBUG(10,("fork_echo_handler: main[%d] echo_child[%d]\n", (int)sys_getpid(), child)); + + /* + * Without smb signing this is the same as the normal smbd + * listener. This needs to change once signing comes in. + */ + sconn->smb1.echo_handler.trusted_fde = event_add_fd(smbd_event_context(), + sconn, + sconn->smb1.echo_handler.trusted_fd, + EVENT_FD_READ, + smbd_server_echo_handler, + sconn); + if (sconn->smb1.echo_handler.trusted_fde == NULL) { + DEBUG(1, ("event_add_fd failed\n")); + goto fail; + } + + return true; + +fail: + if (listener_pipe[0] != -1) { + close(listener_pipe[0]); + } + if (listener_pipe[1] != -1) { + close(listener_pipe[1]); + } + sconn->smb1.echo_handler.trusted_fd = -1; + if (sconn->smb1.echo_handler.socket_lock_fd != -1) { + close(sconn->smb1.echo_handler.socket_lock_fd); + } + sconn->smb1.echo_handler.trusted_fd = -1; + sconn->smb1.echo_handler.socket_lock_fd = -1; + return false; +} + /**************************************************************************** Process commands from the client ****************************************************************************/ void smbd_process(void) { + TALLOC_CTX *frame = talloc_stackframe(); + char remaddr[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"); + } + + smbd_server_conn->smb1.echo_handler.socket_lock_fd = -1; + smbd_server_conn->smb1.echo_handler.trusted_fd = -1; + + /* 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 * parameters before doing any parsing of packets passed to us by the @@ -1968,16 +2614,115 @@ void smbd_process(void) unsigned char buf[5] = {0x83, 0, 0, 1, 0x81}; DEBUG( 1, ("Connection denied from %s\n", client_addr(get_client_fd(),addr,sizeof(addr)) ) ); - (void)srv_send_smb(smbd_server_fd(),(char *)buf,false); + (void)srv_send_smb(smbd_server_fd(),(char *)buf, false, + 0, false, NULL); exit_server_cleanly("connection denied"); } - max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); + static_init_rpc; - smbd_server_conn = talloc_zero(smbd_event_context(), struct smbd_server_connection); - if (!smbd_server_conn) { - exit_server("failed to create smbd_server_connection"); + init_modules(); + + smb_perfcount_init(); + + if (!init_account_policy()) { + exit_server("Could not open account policy tdb.\n"); + } + + if (*lp_rootdir()) { + if (chroot(lp_rootdir()) != 0) { + DEBUG(0,("Failed to change root to %s\n", lp_rootdir())); + exit_server("Failed to chroot()"); + } + if (chdir("/") == -1) { + DEBUG(0,("Failed to chdir to / on chroot to %s\n", lp_rootdir())); + exit_server("Failed to chroot()"); + } + DEBUG(0,("Changed root to %s\n", lp_rootdir())); + } + + if (!srv_init_signing(smbd_server_conn)) { + exit_server("Failed to init smb_signing"); + } + + if (lp_async_smb_echo_handler() && !fork_echo_handler(smbd_server_conn)) { + exit_server("Failed to fork echo handler"); + } + + /* Setup oplocks */ + if (!init_oplocks(smbd_messaging_context())) + exit_server("Failed to init oplocks"); + + /* Setup aio signal handler. */ + initialize_async_io_handler(); + + /* 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))); + } + } + +#endif + + max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); + smbd_server_conn->fde = event_add_fd(smbd_event_context(), smbd_server_conn, smbd_server_fd(), @@ -1988,9 +2733,12 @@ void smbd_process(void) exit_server("failed to create smbd_server_connection fde"); } + TALLOC_FREE(frame); + while (True) { NTSTATUS status; - TALLOC_CTX *frame = talloc_stackframe_pool(8192); + + frame = talloc_stackframe_pool(8192); errno = 0; @@ -1999,9 +2747,35 @@ void smbd_process(void) !NT_STATUS_IS_OK(status)) { DEBUG(3, ("smbd_server_connection_loop_once failed: %s," " exiting\n", nt_errstr(status))); - return; + break; } TALLOC_FREE(frame); } + + exit_server_cleanly(NULL); +} + +bool req_is_in_chain(struct smb_request *req) +{ + if (req->vwv != (uint16_t *)(req->inbuf+smb_vwv)) { + /* + * We're right now handling a subsequent request, so we must + * be in a chain + */ + return true; + } + + if (!is_andx_req(req->cmd)) { + return false; + } + + if (req->wct < 2) { + /* + * Okay, an illegal request, but definitely not chained :-) + */ + return false; + } + + return (CVAL(req->vwv+0, 0) != 0xFF); }