From a0cd12e221af54e00aa7dd971c080881da8b32ac Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 30 Sep 1997 02:38:19 +0000 Subject: dir.c: more pstrcpys. local.h: Add OPLOCK_BREAK_TIMEOUT. password.c: Fix for paranoia password server security bug. proto.h: Updated. reply.c: Oplock changes. server.c: Massive oplock changes - nearly there.... smb.h: oplock definitions. util.c: Add local message processing queues for oplocks. Jeremy (jallison@whistle.com) (This used to be commit 92f1553db2cdf6f32881eb984a87050cf3e4760b) --- source3/include/local.h | 5 + source3/include/proto.h | 7 +- source3/include/smb.h | 41 +++++ source3/lib/util.c | 211 +++++++++++++++++----- source3/smbd/dir.c | 6 +- source3/smbd/password.c | 45 +++-- source3/smbd/reply.c | 11 +- source3/smbd/server.c | 456 ++++++++++++++++++++++++++++++++++++++++-------- 8 files changed, 637 insertions(+), 145 deletions(-) diff --git a/source3/include/local.h b/source3/include/local.h index 9548bf74b6..e7eff2a300 100644 --- a/source3/include/local.h +++ b/source3/include/local.h @@ -159,4 +159,9 @@ /* the directory to sit in when idle */ /* #define IDLE_DIR "/" */ +/* Timout (in seconds) to wait for an oplock breal + message to return. */ + +#define OPLOCK_BREAK_TIMEOUT 120 + #endif diff --git a/source3/include/proto.h b/source3/include/proto.h index 8903437d00..7a1ccc626f 100644 --- a/source3/include/proto.h +++ b/source3/include/proto.h @@ -719,6 +719,9 @@ int find_service(char *service); int cached_error_packet(char *inbuf,char *outbuf,int fnum,int line); int unix_error_packet(char *inbuf,char *outbuf,int def_class,uint32 def_code,int line); int error_packet(char *inbuf,char *outbuf,int error_class,uint32 error_code,int line); +BOOL oplock_break(uint32 dev, uint32 inode); +BOOL request_oplock_break(min_share_mode_entry *share_entry, + uint32 dev, uint32 inode); BOOL snum_used(int snum); BOOL reload_services(BOOL test); int setup_groups(char *user, int uid, int gid, int *p_ngroups, @@ -925,7 +928,9 @@ int read_data(int fd,char *buffer,int N); int write_data(int fd,char *buffer,int N); int transfer_file(int infd,int outfd,int n,char *header,int headlen,int align); int read_smb_length(int fd,char *inbuf,int timeout); -BOOL receive_smb(int fd,char *buffer,int timeout); +BOOL receive_smb(int fd,char *buffer, int timeout); +BOOL receive_local_message(int fd, char *buffer, int buffer_len, int timeout); +BOOL push_local_message(char *buf, int msg_len); BOOL receive_message_or_smb(int smbfd, int oplock_fd, char *buffer, int buffer_len, int timeout, BOOL *got_smb); diff --git a/source3/include/smb.h b/source3/include/smb.h index 07614194f7..c8de001fda 100644 --- a/source3/include/smb.h +++ b/source3/include/smb.h @@ -1014,6 +1014,17 @@ extern int unix_ERR_code; #define EXTENDED_OPLOCK_REQUEST(inbuf) (((SVAL(inbuf,smb_vwv2)|(1<<1))>>1) | \ ((SVAL(inbuf,smb_vwv2)|(1<<2))>>1)) +/* Lock types. */ +#define LOCKING_ANDX_SHARED_LOCK 0x1 +#define LOCKING_ANDX_OPLOCK_RELEASE 0x2 +#define LOCKING_ANDX_CHANGE_LOCKTYPE 0x4 +#define LOCKING_ANDX_CANCEL_LOCK 0x8 +#define LOCKING_ANDX_LARGE_FILES 0x10 + +/* Oplock levels */ +#define OPLOCKLEVEL_NONE 0 +#define OPLOCKLEVEL_II 1 + /* * Bits we test with. */ @@ -1023,4 +1034,34 @@ extern int unix_ERR_code; #define CORE_OPLOCK_GRANTED (1<<5) #define EXTENDED_OPLOCK_GRANTED (1<<15) +/* + * Loopback command offsets. + */ + +#define UDP_CMD_LEN_OFFSET 0 +#define UDP_CMD_PORT_OFFSET 4 +#define UDP_CMD_HEADER_LEN 6 + +#define UDP_MESSAGE_CMD_OFFSET 0 + +/* + * Oplock break command code to send over the udp socket. + * + * Form of this is : + * + * 0 2 6 10 + * +----+--------+--------+--------+ + * | cmd| pid | dev | inode | + * +----+--------+--------+--------+ + */ + +#define OPLOCK_BREAK_CMD 0x1 +#define OPLOCK_BREAK_PID_OFFSET 2 +#define OPLOCK_BREAK_DEV_OFFSET 6 +#define OPLOCK_BREAK_INODE_OFFSET 10 +#define OPLOCK_BREAK_MSG_LEN 14 + + +#define CMD_REPLY 0x8000 + /* _SMB_H */ diff --git a/source3/lib/util.c b/source3/lib/util.c index 05dd619813..056e7e18db 100644 --- a/source3/lib/util.c +++ b/source3/lib/util.c @@ -2270,10 +2270,11 @@ int read_smb_length(int fd,char *inbuf,int timeout) /**************************************************************************** - read an smb from a fd. + read an smb from a fd. Note that the buffer *MUST* be of size + BUFFER_SIZE+SAFETY_MARGIN. The timeout is in milli seconds ****************************************************************************/ -BOOL receive_smb(int fd,char *buffer,int timeout) +BOOL receive_smb(int fd,char *buffer, int timeout) { int len,ret; @@ -2301,9 +2302,134 @@ BOOL receive_smb(int fd,char *buffer,int timeout) } #ifdef USE_OPLOCKS +/**************************************************************************** + read a message from a udp fd. +The timeout is in milli seconds +****************************************************************************/ +BOOL receive_local_message(int fd, char *buffer, int buffer_len, int timeout) +{ + struct sockaddr_in from; + int fromlen = sizeof(from); + int32 msg_len = 0; + + if(timeout != 0) + { + struct timeval to; + fd_set fds; + int selrtn; + + FD_ZERO(&fds); + FD_SET(fd,&fds); + + to.tv_sec = timeout / 1000; + to.tv_usec = (timeout % 1000) * 1000; + + selrtn = sys_select(&fds,&to); + + /* Check if error */ + if(selrtn == -1) + { + /* something is wrong. Maybe the socket is dead? */ + smb_read_error = READ_ERROR; + return False; + } + + /* Did we timeout ? */ + if (selrtn == 0) + { + smb_read_error = READ_TIMEOUT; + return False; + } + } + + /* + * Read a loopback udp message. + */ + msg_len = recvfrom(fd, &buffer[UDP_CMD_HEADER_LEN], + buffer_len - UDP_CMD_HEADER_LEN, 0, + (struct sockaddr *)&from, &fromlen); + + if(msg_len < 0) + { + DEBUG(0,("receive_local_message. Error in recvfrom. (%s).\n",strerror(errno))); + return False; + } + + /* Validate message length. */ + if(msg_len > (buffer_len - UDP_CMD_HEADER_LEN)) + { + DEBUG(0,("receive_local_message: invalid msg_len (%d) max can be %d\n", + msg_len, + buffer_len - UDP_CMD_HEADER_LEN)); + return False; + } + + /* Validate message from address (must be localhost). */ + if(from.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) + { + DEBUG(0,("receive_local_message: invalid 'from' address \ +(was %x should be 127.0.0.1\n", from.sin_addr.s_addr)); + return False; + } + + /* Setup the message header */ + SIVAL(buffer,UDP_CMD_LEN_OFFSET,msg_len); + SSVAL(buffer,UDP_CMD_PORT_OFFSET,ntohs(from.sin_port)); + + return True; +} + +/**************************************************************************** + structure to hold a linked list of local udp messages. + for processing. +****************************************************************************/ + +typedef struct _udp_message_list { + struct _udp_message_list *msg_next; + char *msg_buf; + int msg_len; +} udp_message_list; + +static udp_message_list *udp_msg_head = NULL; + +/**************************************************************************** + Function to push a linked list of local udp messages ready + for processing. +****************************************************************************/ +BOOL push_local_message(char *buf, int msg_len) +{ + udp_message_list *msg = (udp_message_list *)malloc(sizeof(udp_message_list)); + + if(msg == NULL) + { + DEBUG(0,("push_local_message: malloc fail (1)\n")); + return False; + } + + msg->msg_buf = (char *)malloc(msg_len); + if(msg->msg_buf == NULL) + { + DEBUG(0,("push_local_message: malloc fail (2)\n")); + free((char *)msg); + return False; + } + + memcpy(msg->msg_buf, buf, msg_len); + msg->msg_len = msg_len; + + msg->msg_next = udp_msg_head; + udp_msg_head = msg; + + return True; +} + /**************************************************************************** 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) return this first. + 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 @@ -2322,7 +2448,24 @@ BOOL receive_message_or_smb(int smbfd, int oplock_fd, struct timeval to; *got_smb = False; - + + /* + * Check to see if we already have a message on the udp queue. + * If so - copy and return it. + */ + + if(udp_msg_head) + { + udp_message_list *msg = udp_msg_head; + memcpy(buffer, msg->msg_buf, MIN(buffer_len, msg->msg_len)); + udp_msg_head = msg->msg_next; + + /* Free the message we just copied. */ + free((char *)msg->msg_buf); + free((char *)msg); + return True; + } + FD_ZERO(&fds); FD_SET(smbfd,&fds); FD_SET(oplock_fd,&fds); @@ -2352,34 +2495,8 @@ BOOL receive_message_or_smb(int smbfd, int oplock_fd, } else { - /* - * Read a udp message. - */ - struct sockaddr_in from; - int fromlen = sizeof(from); - int32 msg_len = 0; - uint16 port = 0; - - msg_len = recvfrom(oplock_fd, &buffer[6+sizeof(struct in_addr)], - buffer_len - (6 + sizeof(struct in_addr)), 0, - (struct sockaddr *)&from, &fromlen); - - if(msg_len < 0) - { - DEBUG(0,("Invalid loopback packet ! (%s).\n",strerror(errno))); - return False; - } - - port = ntohs(from.sin_port); - - /* Setup the message header */ - SIVAL(buffer,0,msg_len); - SSVAL(buffer,4,port); - memcpy(&buffer[6],(char *)&from.sin_addr,sizeof(struct in_addr)); - + return receive_local_message(oplock_fd, buffer, buffer_len, 0); } - - return True; } #endif /* USE_OPLOCKS */ @@ -3713,10 +3830,10 @@ char *readdirname(void *p) return(dname); } -/* - * Utility function used to decide if the last component - * of a path matches a (possibly wildcarded) entry in a namelist. - */ +/******************************************************************* + Utility function used to decide if the last component + of a path matches a (possibly wildcarded) entry in a namelist. +********************************************************************/ BOOL is_in_path(char *name, name_compare_entry *namelist) { @@ -3763,19 +3880,19 @@ BOOL is_in_path(char *name, name_compare_entry *namelist) return False; } -/* - * Strip a '/' separated list into an array of - * name_compare_enties structures suitable for - * passing to is_in_path(). We do this for - * speed so we can pre-parse all the names in the list - * and don't do it for each call to is_in_path(). - * namelist is modified here and is assumed to be - * a copy owned by the caller. - * We also check if the entry contains a wildcard to - * remove a potentially expensive call to mask_match - * if possible. - */ - +/******************************************************************* + Strip a '/' separated list into an array of + name_compare_enties structures suitable for + passing to is_in_path(). We do this for + speed so we can pre-parse all the names in the list + and don't do it for each call to is_in_path(). + namelist is modified here and is assumed to be + a copy owned by the caller. + We also check if the entry contains a wildcard to + remove a potentially expensive call to mask_match + if possible. +********************************************************************/ + void set_namearray(name_compare_entry **ppname_array, char *namelist) { char *name_end; diff --git a/source3/smbd/dir.c b/source3/smbd/dir.c index 567bc14424..316b58818f 100644 --- a/source3/smbd/dir.c +++ b/source3/smbd/dir.c @@ -470,12 +470,12 @@ BOOL get_dir_entry(int cnum,char *mask,int dirtype,char *fname,int *size,int *mo if (isrootdir && (strequal(filename,"..") || strequal(filename,"."))) continue; - strcpy(fname,filename); + pstrcpy(fname,filename); *path = 0; - strcpy(path,Connections[cnum].dirpath); + pstrcpy(path,Connections[cnum].dirpath); if(needslash) strcat(path,"/"); - strcpy(pathreal,path); + pstrcpy(pathreal,path); strcat(path,fname); strcat(pathreal,dname); if (sys_stat(pathreal,&sbuf) != 0) diff --git a/source3/smbd/password.c b/source3/smbd/password.c index 35f73eab2d..f4d94791cf 100644 --- a/source3/smbd/password.c +++ b/source3/smbd/password.c @@ -1504,13 +1504,14 @@ BOOL check_hosts_equiv(char *user) int password_client = -1; static fstring pserver; +static char *secserver_inbuf = NULL; /**************************************************************************** attempted support for server level security ****************************************************************************/ BOOL server_cryptkey(char *buf) { - pstring inbuf,outbuf; + pstring outbuf; fstring pass_protocol; extern fstring remote_machine; char *p; @@ -1519,6 +1520,14 @@ BOOL server_cryptkey(char *buf) struct in_addr dest_ip; int port = SMB_PORT; BOOL ret; + + if(secserver_inbuf == NULL) { + secserver_inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); + if(secserver_inbuf == NULL) { + DEBUG(0,("server_cryptkey: malloc fail for input buffer.\n")); + return False; + } + } if (password_client >= 0) close(password_client); @@ -1530,7 +1539,7 @@ BOOL server_cryptkey(char *buf) strcpy(pass_protocol,"NT LM 0.12"); } - bzero(inbuf,sizeof(inbuf)); + bzero(secserver_inbuf,BUFFER_SIZE + SAFETY_MARGIN); bzero(outbuf,sizeof(outbuf)); for (p=strtok(lp_passwordserver(),LIST_SEP); p ; p = strtok(NULL,LIST_SEP)) { @@ -1596,8 +1605,8 @@ BOOL server_cryptkey(char *buf) send_smb(password_client,outbuf); - if (!receive_smb(password_client,inbuf,5000) || - CVAL(inbuf,0) != 0x82) { + if (!receive_smb(password_client,secserver_inbuf,5000) || + CVAL(secserver_inbuf,0) != 0x82) { DEBUG(1,("%s rejected the session\n",pserver)); close(password_client); password_client = -1; return(False); @@ -1618,21 +1627,21 @@ BOOL server_cryptkey(char *buf) SSVAL(outbuf,smb_flg2,0x1); send_smb(password_client,outbuf); - ret = receive_smb(password_client,inbuf,5000); + ret = receive_smb(password_client,secserver_inbuf,5000); - if (!ret || CVAL(inbuf,smb_rcls) || SVAL(inbuf,smb_vwv0)) { + if (!ret || CVAL(secserver_inbuf,smb_rcls) || SVAL(secserver_inbuf,smb_vwv0)) { DEBUG(1,("%s rejected the protocol\n",pserver)); close(password_client); password_client= -1; return(False); } - if (!(CVAL(inbuf,smb_vwv1) & 1)) { + if (!(CVAL(secserver_inbuf,smb_vwv1) & 1)) { DEBUG(1,("%s isn't in user level security mode\n",pserver)); close(password_client); password_client= -1; return(False); } - memcpy(buf,inbuf,smb_len(inbuf)+4); + memcpy(buf,secserver_inbuf,smb_len(secserver_inbuf)+4); DEBUG(3,("password server OK\n")); @@ -1644,15 +1653,23 @@ attempted support for server level security ****************************************************************************/ BOOL server_validate(char *buf) { - pstring inbuf,outbuf; + pstring outbuf; BOOL ret; + if(secserver_inbuf == NULL) { + secserver_inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); + if(secserver_inbuf == NULL) { + DEBUG(0,("server_validate: malloc fail for input buffer.\n")); + return False; + } + } + if (password_client < 0) { DEBUG(1,("%s not connected\n",pserver)); return(False); } - bzero(inbuf,sizeof(inbuf)); + bzero(secserver_inbuf,BUFFER_SIZE + SAFETY_MARGIN); memcpy(outbuf,buf,sizeof(outbuf)); /* send a session setup command */ @@ -1662,18 +1679,18 @@ BOOL server_validate(char *buf) set_message(outbuf,smb_numwords(outbuf),smb_buflen(outbuf),False); - SCVAL(inbuf,smb_rcls,1); + SCVAL(secserver_inbuf,smb_rcls,1); send_smb(password_client,outbuf); - ret = receive_smb(password_client,inbuf,5000); + ret = receive_smb(password_client,secserver_inbuf,5000); - if (!ret || CVAL(inbuf,smb_rcls) != 0) { + if (!ret || CVAL(secserver_inbuf,smb_rcls) != 0) { DEBUG(1,("password server %s rejected the password\n",pserver)); return(False); } /* if logged in as guest then reject */ - if ((SVAL(inbuf,smb_vwv2) & 1) != 0) { + if ((SVAL(secserver_inbuf,smb_vwv2) & 1) != 0) { DEBUG(1,("password server %s gave us guest only\n",pserver)); return(False); } diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c index c1c42be801..8987e7c0c2 100644 --- a/source3/smbd/reply.c +++ b/source3/smbd/reply.c @@ -42,6 +42,7 @@ extern BOOL short_case_preserve; extern pstring sesssetup_user; extern fstring myworkgroup; extern int Client; +extern int global_oplock_break; /* this macro should always be used to extract an fnum (smb_fid) from a packet to ensure chaining works correctly */ @@ -388,7 +389,9 @@ int reply_sesssetup_and_X(char *inbuf,char *outbuf,int length,int bufsize) if (Protocol < PROTOCOL_NT1) { smb_apasslen = SVAL(inbuf,smb_vwv7); if (smb_apasslen > MAX_PASSWORD_LENGTH) + { overflow_attack(smb_apasslen); + } memcpy(smb_apasswd,smb_buf(inbuf),smb_apasslen); pstrcpy(user,smb_buf(inbuf)+smb_apasslen); @@ -1163,7 +1166,7 @@ int reply_open(char *inbuf,char *outbuf) SSVAL(outbuf,smb_vwv6,rmode); if (oplock_request && lp_fake_oplocks(SNUM(cnum))) { - fsp->granted_oplock = True; + CVAL(outbuf,smb_flg) |= CORE_OPLOCK_GRANTED; } if(fsp->granted_oplock) @@ -1250,7 +1253,7 @@ int reply_open_and_X(char *inbuf,char *outbuf,int length,int bufsize) } if (oplock_request && lp_fake_oplocks(SNUM(cnum))) { - fsp->granted_oplock = True; + smb_action |= EXTENDED_OPLOCK_GRANTED; } if(fsp->granted_oplock) @@ -1377,7 +1380,7 @@ int reply_mknew(char *inbuf,char *outbuf) SSVAL(outbuf,smb_vwv0,fnum); if (oplock_request && lp_fake_oplocks(SNUM(cnum))) { - fsp->granted_oplock = True; + CVAL(outbuf,smb_flg) |= CORE_OPLOCK_GRANTED; } if(fsp->granted_oplock) @@ -1453,7 +1456,7 @@ int reply_ctemp(char *inbuf,char *outbuf) strcpy(smb_buf(outbuf) + 1,fname2); if (oplock_request && lp_fake_oplocks(SNUM(cnum))) { - fsp->granted_oplock = True; + CVAL(outbuf,smb_flg) |= CORE_OPLOCK_GRANTED; } if(fsp->granted_oplock) diff --git a/source3/smbd/server.c b/source3/smbd/server.c index d2ad803c9c..708a2c272b 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -89,9 +89,11 @@ static int num_connections_open = 0; int oplock_sock = -1; uint16 oplock_port = 0; /* Current number of oplocks we have outstanding. */ -uint32 oplocks_open = 0; +uint32 global_oplocks_open = 0; #endif /* USE_OPLOCKS */ +BOOL global_oplock_break = False; + extern fstring remote_machine; pstring OriginalDir; @@ -1355,17 +1357,17 @@ void close_file(int fnum) fs_p->open = False; Connections[cnum].num_files_open--; if(fs_p->wbmpx_ptr) - { - free((char *)fs_p->wbmpx_ptr); - fs_p->wbmpx_ptr = NULL; - } + { + free((char *)fs_p->wbmpx_ptr); + fs_p->wbmpx_ptr = NULL; + } #if USE_MMAP if(fs_p->mmap_ptr) - { - munmap(fs_p->mmap_ptr,fs_p->mmap_size); - fs_p->mmap_ptr = NULL; - } + { + munmap(fs_p->mmap_ptr,fs_p->mmap_size); + fs_p->mmap_ptr = NULL; + } #endif if (lp_share_modes(SNUM(cnum))) @@ -1668,12 +1670,15 @@ void open_file_shared(int fnum,int cnum,char *fname,int share_mode,int ofun, do { + broke_oplock = False; for(i = 0; i < num_shares; i++) { + min_share_mode_entry *share_entry = &old_shares[i]; + /* someone else has a share lock on it, check to see if we can too */ - if(check_share_mode(&old_shares[i], deny_mode, fname, fcbopen, &flags) == False) + if(check_share_mode(share_entry, deny_mode, fname, fcbopen, &flags) == False) { free((char *)old_shares); unlock_share_entry(cnum, dev, inode, token); @@ -1688,23 +1693,31 @@ void open_file_shared(int fnum,int cnum,char *fname,int share_mode,int ofun, * has an oplock on this file. If so we must break it before * continuing. */ - if(old_shares[i].op_type != 0) + if(share_entry->op_type & (EXCLUSIVE_OPLOCK|BATCH_OPLOCK)) { + + DEBUG(5,("open file shared: breaking oplock (%x) on file %s, \ +dev = %x, inode = %x\n", share_entry->op_type, fname, dev, inode)); + /* Oplock break.... */ unlock_share_entry(cnum, dev, inode, token); -#if 0 /* Work in progress..... */ - if(break_oplock()) + if(request_oplock_break(share_entry, dev, inode) == False) { free((char *)old_shares); - /* Error condition here... */ + DEBUG(0,("open file shared: FAILED when breaking oplock (%x) on file %s, \ +dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode)); + errno = EACCES; + unix_ERR_class = ERRDOS; + unix_ERR_code = ERRbadshare; + return; } lock_share_entry(cnum, dev, inode, &token); broke_oplock = True; break; -#endif } #endif /* USE_OPLOCKS */ - } + } /* end for */ + if(broke_oplock) { free((char *)old_shares); @@ -2312,6 +2325,52 @@ static BOOL open_sockets(BOOL is_daemon,int port) return True; } +/**************************************************************************** + process an smb from the client - split out from the process() code so + it can be used by the oplock break code. +****************************************************************************/ + +static void process_smb(char *inbuf, char *outbuf) +{ + extern int Client; + static int trans_num = 0; + + int msg_type = CVAL(inbuf,0); + int32 len = smb_len(outbuf); + int nread = len + 4; + + DEBUG(6,("got message type 0x%x of len 0x%x\n",msg_type,len)); + DEBUG(3,("%s Transaction %d of length %d\n",timestring(),trans_num,nread)); + +#ifdef WITH_VTP + if(trans_num == 1 && VT_Check(inbuf)) + { + VT_Process(); + return; + } +#endif + + if (msg_type == 0) + show_msg(inbuf); + + nread = construct_reply(inbuf,outbuf,nread,max_send); + + if(nread > 0) + { + if (CVAL(outbuf,0) == 0) + show_msg(outbuf); + + if (nread != smb_len(outbuf) + 4) + { + DEBUG(0,("ERROR: Invalid message response size! %d %d\n", + nread, smb_len(outbuf))); + } + else + send_smb(Client,outbuf); + } + trans_num++; +} + #ifdef USE_OPLOCKS /**************************************************************************** open the oplock IPC socket communication @@ -2355,33 +2414,324 @@ static BOOL process_local_message(int oplock_sock, char *buffer, int buf_size) { int32 msg_len; int16 from_port; - struct in_addr from_addr; char *msg_start; - msg_len = IVAL(buffer,0); - from_port = SVAL(buffer,4); - memcpy((char *)&from_addr, &buffer[6], sizeof(struct in_addr)); + msg_len = IVAL(buffer,UDP_CMD_LEN_OFFSET); + from_port = SVAL(buffer,UDP_CMD_PORT_OFFSET); + + msg_start = &buffer[UDP_CMD_HEADER_LEN]; + + DEBUG(5,("process_local_message: Got a message of length %d from port (%d)\n", + msg_len, from_port)); + + /* Switch on message command - currently OPLOCK_BREAK_CMD is the + only valid request. */ + + switch(SVAL(msg_start,UDP_MESSAGE_CMD_OFFSET)) + { + case OPLOCK_BREAK_CMD: + /* Ensure that the msg length is correct. */ + if(msg_len != OPLOCK_BREAK_MSG_LEN) + { + DEBUG(0,("process_local_message: incorrect length for OPLOCK_BREAK_CMD (was %d, \ +should be %d).\n", msg_len, OPLOCK_BREAK_MSG_LEN)); + return False; + } + { + uint32 remotepid = IVAL(msg_start,OPLOCK_BREAK_PID_OFFSET); + uint32 dev = IVAL(msg_start,OPLOCK_BREAK_DEV_OFFSET); + uint32 inode = IVAL(msg_start, OPLOCK_BREAK_INODE_OFFSET); + struct sockaddr_in toaddr; + + DEBUG(5,("process_local_message: oplock break request from \ +pid %d, dev %d, inode %d\n", remotepid, dev, inode)); + + /* + * If we have no record of any currently open oplocks, + * it's not an error, as a close command may have + * just been issued on the file that was oplocked. + * Just return success in this case. + */ + + if(global_oplocks_open != 0) + { + if(oplock_break(dev, inode) == False) + { + DEBUG(0,("process_local_message: oplock break failed - \ +not returning udp message.\n")); + return False; + } + } + else + { + DEBUG(3,("process_local_message: oplock break requested with no outstanding \ +oplocks. Returning success.\n")); + } + + /* Send the message back after OR'ing in the 'REPLY' bit. */ + SSVAL(msg_start,UDP_MESSAGE_CMD_OFFSET,OPLOCK_BREAK_CMD | CMD_REPLY); + + bzero((char *)&toaddr,sizeof(toaddr)); + toaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + toaddr.sin_port = htons(from_port); + toaddr.sin_family = AF_INET; + + if(sendto( oplock_sock, msg_start, OPLOCK_BREAK_MSG_LEN, 0, + (struct sockaddr *)&toaddr, sizeof(toaddr)) < 0) + { + DEBUG(0,("process_local_message: sendto process %d failed. Errno was %s\n", + remotepid, strerror(errno))); + return False; + } + } + break; + default: + DEBUG(0,("process_local_message: unknown UDP message command code (%x) - ignoring.\n", + (unsigned int)SVAL(msg_start,0))); + return False; + } + return True; +} + +/**************************************************************************** + Process an oplock break directly. +****************************************************************************/ +BOOL oplock_break(uint32 dev, uint32 inode) +{ + extern int Client; + static char *inbuf = NULL; + static char *outbuf = NULL; + files_struct *fsp = NULL; + int fnum; + + if(inbuf == NULL) + { + inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); + if(inbuf == NULL) { + DEBUG(0,("oplock_break: malloc fail for input buffer.\n")); + return False; + } + outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); + if(outbuf == NULL) { + DEBUG(0,("oplock_break: malloc fail for output buffer.\n")); + free(inbuf); + inbuf = NULL; + return False; + } + } + + /* We need to search the file open table for the + entry containing this dev and inode, and ensure + we have an oplock on it. */ + for( fnum = 0; fnum < MAX_OPEN_FILES; fnum++) + { + if(OPEN_FNUM(fnum)) + { + fsp = &Files[fnum]; + if((fsp->fd_ptr->dev == dev) && (fsp->fd_ptr->inode == inode)) + break; + } + } + + if(fsp == NULL) + { + /* The file could have been closed in the meantime - return success. */ + DEBUG(3,("oplock_break: cannot find open file with dev = %x, inode = %x (fnum = %d) \ +allowing break to succeed.\n", dev, inode, fnum)); + return True; + } + + /* Ensure we have an oplock on the file */ + + /* Question - can a client asynchronously break an oplock ? Would it + ever do so ? If so this test is invalid for external smbd oplock + breaks and we should return True in these cases (JRA). + */ + + if(!fsp->granted_oplock) + { + DEBUG(0,("oplock_break: file %s (fnum = %d, dev = %x, inode = %x) has no oplock.\n", + fsp->name, fnum, dev, inode)); + return False; + } + + /* Now comes the horrid part. We must send an oplock break to the client, + and then process incoming messages until we get a close or oplock release. + */ + + /* Prepare the SMBlockingX message. */ + bzero(outbuf,smb_size); + set_message(outbuf,8,0,True); + + SCVAL(outbuf,smb_com,SMBlockingX); + SSVAL(outbuf,smb_tid,fsp->cnum); + SSVAL(outbuf,smb_pid,0xFFFF); + SSVAL(outbuf,smb_uid,0); + SSVAL(outbuf,smb_mid,0xFFFF); + SCVAL(outbuf,smb_vwv0,0xFF); + SSVAL(outbuf,smb_vwv2,fnum); + SCVAL(outbuf,smb_vwv3,LOCKING_ANDX_OPLOCK_RELEASE); + /* Change this when we have level II oplocks. */ + SCVAL(outbuf,smb_vwv3+1,OPLOCKLEVEL_NONE); + + send_smb(Client, outbuf); + + global_oplock_break = True; + + /* Process incoming messages. */ + while(global_oplock_break && OPEN_FNUM(fnum)) + { + if(receive_smb(Client,inbuf,OPLOCK_BREAK_TIMEOUT * 1000) == False) + { + if (smb_read_error == READ_EOF) + { + DEBUG(3,("oplock_break: end of file from client\n")); + return False; + } + + if (smb_read_error == READ_ERROR) + { + DEBUG(3,("oplock_break: receive_smb error (%s)\n", + strerror(errno))); + return False; + } + } + process_smb(inbuf, outbuf); + } + + return True; +} + +/**************************************************************************** +Send an oplock break message to another smbd process. If the oplock is held +by the local smbd then call the oplock break function directly. +****************************************************************************/ - msg_start = &buffer[6 + sizeof(struct in_addr)]; +BOOL request_oplock_break(min_share_mode_entry *share_entry, + uint32 dev, uint32 inode) +{ + char op_break_msg[OPLOCK_BREAK_MSG_LEN]; + struct sockaddr_in addr_out; + int pid = getpid(); + + if(pid == share_entry->pid) + { + /* We are breaking our own oplock, make sure it's us. */ + if(share_entry->op_port != oplock_port) + { + DEBUG(0,("request_oplock_break: corrupt share mode entry - pid = %x, port = %d \ +should be %d\n", pid, share_entry->op_port, oplock_port)); + return False; + } + /* Call oplock break direct. */ + return oplock_break(dev, inode); + } + + /* We need to send a OPLOCK_BREAK_CMD message to the + port in the share mode entry. */ - /* Validate message length. */ - if(msg_len > (buf_size - (6 + sizeof(struct in_addr)))) + SSVAL(op_break_msg,UDP_MESSAGE_CMD_OFFSET,OPLOCK_BREAK_CMD); + SIVAL(op_break_msg,OPLOCK_BREAK_PID_OFFSET,pid); + SIVAL(op_break_msg,OPLOCK_BREAK_DEV_OFFSET,dev); + SIVAL(op_break_msg,OPLOCK_BREAK_INODE_OFFSET,inode); + + /* set the address and port */ + bzero((char *)&addr_out,sizeof(addr_out)); + addr_out.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr_out.sin_port = htons( share_entry->op_port ); + addr_out.sin_family = AF_INET; + + DEBUG(3,("request_oplock_break: sending a oplock break message to pid %d on port %d \ +for dev = %x, inode = %x\n", share_entry->pid, share_entry->op_port, dev, inode)); + + if(sendto(oplock_sock,op_break_msg,OPLOCK_BREAK_MSG_LEN,0, + (struct sockaddr *)&addr_out,sizeof(addr_out)) < 0) { - DEBUG(0,("process_local_message: invalid msg_len (%d) max can be %d\n", - msg_len, buf_size - (6 + sizeof(struct in_addr)))); + DEBUG(0,("request_oplock_break: failed when sending a oplock break message \ +to pid %d on port %d for dev = %x, inode = %x. Error was %s\n", + share_entry->pid, share_entry->op_port, dev, inode, + strerror(errno))); return False; } - /* Validate message from address (must be localhost). */ - if(from_addr.s_addr != htonl(INADDR_LOOPBACK)) + /* + * Now we must await the oplock broken message coming back + * from the target smbd process. Timeout if it fails to + * return in OPLOCK_BREAK_TIMEOUT seconds. + * While we get messages that aren't ours, loop. + */ + + while(1) { - DEBUG(0,("process_local_message: invalid 'from' address \ -(was %x should be 127.0.0.1\n", from_addr.s_addr)); - return False; + char op_break_reply[UDP_CMD_HEADER_LEN+OPLOCK_BREAK_MSG_LEN]; + int32 reply_msg_len; + int16 reply_from_port; + char *reply_msg_start; + + if(receive_local_message(oplock_sock, op_break_reply, sizeof(op_break_reply), + OPLOCK_BREAK_TIMEOUT * 1000) == False) + { + if(smb_read_error == READ_TIMEOUT) + DEBUG(0,("request_oplock_break: no response received to oplock break request to \ +pid %d on port %d for dev = %x, inode = %x\n", share_entry->pid, + share_entry->op_port, dev, inode)); + else + DEBUG(0,("request_oplock_break: error in response received to oplock break request to \ +pid %d on port %d for dev = %x, inode = %x. Error was (%s).\n", share_entry->pid, + share_entry->op_port, dev, inode, strerror(errno))); + return False; + } + + /* + * If the response we got was not an answer to our message, but + * was a completely different request, push it onto the pending + * udp message stack so that we can deal with it in the main loop. + * It may be another oplock break request to us. + */ + + /* + * Local note from JRA. There exists the possibility of a denial + * of service attack here by allowing non-root processes running + * on a local machine sending many of these pending messages to + * a smbd port. Currently I'm not sure how to restrict the messages + * I will queue (although I could add a limit to the queue) to + * those received by root processes only. There should be a + * way to make this bulletproof.... + */ + + reply_msg_len = IVAL(op_break_reply,UDP_CMD_LEN_OFFSET); + reply_from_port = SVAL(op_break_reply,UDP_CMD_PORT_OFFSET); + + reply_msg_start = &op_break_reply[UDP_CMD_HEADER_LEN]; + + if(reply_msg_len != OPLOCK_BREAK_MSG_LEN) + { + /* Ignore it. */ + DEBUG(0,("request_oplock_break: invalid message length received. Ignoring\n")); + continue; + } + + if(((SVAL(reply_msg_start,UDP_MESSAGE_CMD_OFFSET) & CMD_REPLY) == 0) || + (reply_from_port != share_entry->op_port) || + (memcmp(&reply_msg_start[OPLOCK_BREAK_PID_OFFSET], + &op_break_msg[OPLOCK_BREAK_PID_OFFSET], + OPLOCK_BREAK_MSG_LEN - OPLOCK_BREAK_PID_OFFSET) != 0)) + { + DEBUG(3,("request_oplock_break: received other message whilst awaiting \ +oplock break response from pid %d on port %d for dev = %x, inode = %x.\n", + share_entry->pid, share_entry->op_port, dev, inode)); + if(push_local_message(op_break_reply, sizeof(op_break_reply)) == False) + return False; + } + + break; } + DEBUG(3,("request_oplock_break: broke oplock.\n")); + return True; } + #endif /* USE_OPLOCKS */ /**************************************************************************** @@ -4056,52 +4406,6 @@ int construct_reply(char *inbuf,char *outbuf,int size,int bufsize) return(outsize); } -/**************************************************************************** - process an smb from the client - split out from the process() code so - it can be used by the oplock break code. -****************************************************************************/ - -static void process_smb(char *inbuf, char *outbuf) -{ - extern int Client; - static int trans_num = 0; - - int msg_type = CVAL(inbuf,0); - int32 len = smb_len(outbuf); - int nread = len + 4; - - DEBUG(6,("got message type 0x%x of len 0x%x\n",msg_type,len)); - DEBUG(3,("%s Transaction %d of length %d\n",timestring(),trans_num,nread)); - -#ifdef WITH_VTP - if(trans_num == 1 && VT_Check(inbuf)) - { - VT_Process(); - return; - } -#endif - - if (msg_type == 0) - show_msg(inbuf); - - nread = construct_reply(inbuf,outbuf,nread,max_send); - - if(nread > 0) - { - if (CVAL(outbuf,0) == 0) - show_msg(outbuf); - - if (nread != smb_len(outbuf) + 4) - { - DEBUG(0,("ERROR: Invalid message response size! %d %d\n", - nread, smb_len(outbuf))); - } - else - send_smb(Client,outbuf); - } - trans_num++; -} - /**************************************************************************** process commands from the client ****************************************************************************/ -- cgit