Race condition with multiple oplock break requests happens
[samba.git] / source3 / smbd / server.c
index 5f59af1bf198a3824d6f878da64db4bb0ffe8a22..b250572a1f43b73d0bf5ea3ee1575ef38dec1d3f 100644 (file)
@@ -89,7 +89,7 @@ static int num_connections_open = 0;
 int oplock_sock = -1;
 uint16 oplock_port = 0;
 /* Current number of oplocks we have outstanding. */
-uint32 global_oplocks_open = 0;
+int32 global_oplocks_open = 0;
 #endif /* USE_OPLOCKS */
 
 BOOL global_oplock_break = False;
@@ -189,7 +189,7 @@ int dos_mode(int cnum,char *path,struct stat *sbuf)
   int result = 0;
   extern struct current_user current_user;
 
-  DEBUG(5,("dos_mode: %d %s\n", cnum, path));
+  DEBUG(8,("dos_mode: %d %s\n", cnum, path));
 
   if (CAN_WRITE(cnum) && !lp_alternate_permissions(SNUM(cnum))) {
     if (!((sbuf->st_mode & S_IWOTH) ||
@@ -241,15 +241,15 @@ int dos_mode(int cnum,char *path,struct stat *sbuf)
     result |= aHIDDEN;
   }
 
-  DEBUG(5,("dos_mode returning "));
+  DEBUG(8,("dos_mode returning "));
 
-  if (result & aHIDDEN) DEBUG(5, ("h"));
-  if (result & aRONLY ) DEBUG(5, ("r"));
-  if (result & aSYSTEM) DEBUG(5, ("s"));
-  if (result & aDIR   ) DEBUG(5, ("d"));
-  if (result & aARCH  ) DEBUG(5, ("a"));
+  if (result & aHIDDEN) DEBUG(8, ("h"));
+  if (result & aRONLY ) DEBUG(8, ("r"));
+  if (result & aSYSTEM) DEBUG(8, ("s"));
+  if (result & aDIR   ) DEBUG(8, ("d"));
+  if (result & aARCH  ) DEBUG(8, ("a"));
 
-  DEBUG(5,("\n"));
+  DEBUG(8,("\n"));
 
   return(result);
 }
@@ -886,8 +886,7 @@ static void check_for_pipe(char *fname)
 /****************************************************************************
 fd support routines - attempt to do a sys_open
 ****************************************************************************/
-
-int fd_attempt_open(char *fname, int flags, int mode)
+static int fd_attempt_open(char *fname, int flags, int mode)
 {
   int fd = sys_open(fname,flags,mode);
 
@@ -939,7 +938,7 @@ int fd_attempt_open(char *fname, int flags, int mode)
 fd support routines - attempt to find an already open file by dev
 and inode - increments the ref_count of the returned file_fd_struct *.
 ****************************************************************************/
-file_fd_struct *fd_get_already_open(struct stat *sbuf)
+static file_fd_struct *fd_get_already_open(struct stat *sbuf)
 {
   int i;
   file_fd_struct *fd_ptr;
@@ -966,7 +965,7 @@ file_fd_struct *fd_get_already_open(struct stat *sbuf)
 fd support routines - attempt to find a empty slot in the FileFd array.
 Increments the ref_count of the returned entry.
 ****************************************************************************/
-file_fd_struct *fd_get_new()
+static file_fd_struct *fd_get_new()
 {
   int i;
   file_fd_struct *fd_ptr;
@@ -999,8 +998,7 @@ n"));
 fd support routines - attempt to re-open an already open fd as O_RDWR.
 Save the already open fd (we cannot close due to POSIX file locking braindamage.
 ****************************************************************************/
-
-void fd_attempt_reopen(char *fname, int mode, file_fd_struct *fd_ptr)
+static void fd_attempt_reopen(char *fname, int mode, file_fd_struct *fd_ptr)
 {
   int fd = sys_open( fname, O_RDWR, mode);
 
@@ -1020,7 +1018,7 @@ void fd_attempt_reopen(char *fname, int mode, file_fd_struct *fd_ptr)
 fd support routines - attempt to close the file referenced by this fd.
 Decrements the ref_count and returns it.
 ****************************************************************************/
-int fd_attempt_close(file_fd_struct *fd_ptr)
+static int fd_attempt_close(file_fd_struct *fd_ptr)
 {
   DEBUG(3,("fd_attempt_close on file_fd_struct %d, fd = %d, dev = %x, inode = %x, open_flags = %d, ref_count = %d.\n",
           fd_ptr - &FileFd[0],
@@ -1060,6 +1058,7 @@ static void open_file(int fnum,int cnum,char *fname1,int flags,int mode, struct
 
   fsp->open = False;
   fsp->fd_ptr = 0;
+  fsp->granted_oplock = False;
   errno = EPERM;
 
   pstrcpy(fname,fname1);
@@ -1404,7 +1403,8 @@ static int access_table(int new_deny,int old_deny,int old_mode,
   if (new_deny == DENY_ALL || old_deny == DENY_ALL) return(AFAIL);
 
   if (new_deny == DENY_DOS || old_deny == DENY_DOS) {
-    if (old_deny == new_deny && share_pid == getpid()) 
+    int pid = getpid();
+    if (old_deny == new_deny && share_pid == pid) 
        return(AALL);    
 
     if (old_mode == 0) return(AREAD);
@@ -1487,16 +1487,12 @@ BOOL check_file_sharing(int cnum,char *fname)
       {
         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 ((share_entry->share_mode != DENY_DOS) || (share_entry->pid != pid))
-          goto free_and_exit;
-
 #ifdef USE_OPLOCKS
         /* 
-         * The share modes would give us access. Check if someone
-         * has an oplock on this file. If so we must break it before
-         * continuing. 
+         * Break oplocks before checking share modes. See comment in
+         * open_file_shared for details. 
+         * Check if someone has an oplock on this file. If so we must 
+         * break it before continuing. 
          */
         if(share_entry->op_type & BATCH_OPLOCK)
         {
@@ -1518,6 +1514,12 @@ dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode));
           break;
         }
 #endif /* USE_OPLOCKS */
+
+        /* someone else has a share lock on it, check to see 
+           if we can too */
+        if ((share_entry->share_mode != DENY_DOS) || (share_entry->pid != pid))
+          goto free_and_exit;
+
       } /* end for */
 
       if(broke_oplock)
@@ -1727,22 +1729,13 @@ void open_file_shared(int fnum,int cnum,char *fname,int share_mode,int ofun,
         {
           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(share_entry, deny_mode, fname, fcbopen, &flags) == False)
-          {
-            free((char *)old_shares);
-            unlock_share_entry(cnum, dev, inode, token);
-            errno = EACCES;
-            unix_ERR_class = ERRDOS;
-            unix_ERR_code = ERRbadshare;
-            return;
-          }
 #ifdef USE_OPLOCKS
           /* 
-           * The share modes would give us access. Check if someone
-           * has an oplock on this file. If so we must break it before
-           * continuing. 
+           * By observation of NetBench, oplocks are broken *before* share
+           * modes are checked. This allows a file to be closed by the client
+           * if the share mode would deny access and the client has an oplock. 
+           * Check if someone has an oplock on this file. If so we must break 
+           * it before continuing. 
            */
           if(share_entry->op_type & (EXCLUSIVE_OPLOCK|BATCH_OPLOCK))
           {
@@ -1767,6 +1760,19 @@ dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode));
             break;
           }
 #endif /* USE_OPLOCKS */
+
+          /* someone else has a share lock on it, check to see 
+             if we can too */
+          if(check_share_mode(share_entry, deny_mode, fname, fcbopen, &flags) == False)
+          {
+            free((char *)old_shares);
+            unlock_share_entry(cnum, dev, inode, token);
+            errno = EACCES;
+            unix_ERR_class = ERRDOS;
+            unix_ERR_code = ERRbadshare;
+            return;
+          }
+
         } /* end for */
 
         if(broke_oplock)
@@ -1841,7 +1847,7 @@ dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode));
          be extended to level II oplocks (multiple reader
          oplocks). */
 
-      if(oplock_request && (num_share_modes == 0))
+      if(oplock_request && (num_share_modes == 0) && lp_oplocks(SNUM(cnum)))
       {
         fs_p->granted_oplock = True;
         global_oplocks_open++;
@@ -1849,8 +1855,16 @@ dev = %x, inode = %x\n", old_shares[i].op_type, fname, dev, inode));
 
         DEBUG(5,("open_file_shared: granted oplock (%x) on file %s, \
 dev = %x, inode = %x\n", oplock_request, fname, dev, inode));
-      }
 
+      }
+      else
+      {
+        port = 0;
+        oplock_request = 0;
+      }
+#else /* USE_OPLOCKS */
+      oplock_request = 0;
+      port = 0;
 #endif /* USE_OPLOCKS */
       set_share_mode(token, fnum, port, oplock_request);
     }
@@ -2102,7 +2116,7 @@ struct
   {EPERM,ERRDOS,ERRnoaccess},
   {EACCES,ERRDOS,ERRnoaccess},
   {ENOENT,ERRDOS,ERRbadfile},
-  {ENOTDIR,ERRDOS,ERRbaddirectory},
+  {ENOTDIR,ERRDOS,ERRbadpath},
   {EIO,ERRHRD,ERRgeneral},
   {EBADF,ERRSRV,ERRsrverror},
   {EINVAL,ERRSRV,ERRsrverror},
@@ -2123,20 +2137,6 @@ struct
   {0,0,0}
 };
 
-/* Mapping for old clients. */
-
-struct
-{
-  int new_smb_error;
-  int old_smb_error;
-  int protocol_level;
-  enum remote_arch_types valid_ra_type;
-} old_client_errmap[] =
-{
-  {ERRbaddirectory, ERRbadpath, (int)PROTOCOL_NT1, RA_WINNT},
-  {0,0,0}
-};
-
 /****************************************************************************
   create an error packet from errno
 ****************************************************************************/
@@ -2167,29 +2167,6 @@ int unix_error_packet(char *inbuf,char *outbuf,int def_class,uint32 def_code,int
       }
     }
 
-  /* Make sure we don't return error codes that old
-     clients don't understand. */
-
-  /* JRA - unfortunately, WinNT needs some error codes
-     for apps to work correctly, Win95 will break if
-     these error codes are returned. But they both
-     negotiate the *same* protocol. So we need to use
-     the revolting 'remote_arch' enum to tie break.
-
-     There must be a better way of doing this...
-  */
-
-  for(i = 0; old_client_errmap[i].new_smb_error != 0; i++)
-  {
-    if(((Protocol < old_client_errmap[i].protocol_level) ||
-       (old_client_errmap[i].valid_ra_type != get_remote_arch())) &&
-       (old_client_errmap[i].new_smb_error == ecode))
-    {
-      ecode = old_client_errmap[i].old_smb_error;
-      break;
-    }
-  }
-
   return(error_packet(inbuf,outbuf,eclass,ecode,line));
 }
 
@@ -2404,12 +2381,27 @@ static BOOL open_sockets(BOOL is_daemon,int port)
 static void process_smb(char *inbuf, char *outbuf)
 {
   extern int Client;
-  static int trans_num = 0;
-
+  static int trans_num;
   int msg_type = CVAL(inbuf,0);
-  int32 len = smb_len(outbuf);
+  int32 len = smb_len(inbuf);
   int nread = len + 4;
 
+  if (trans_num == 0) {
+         /* on the first packet, check the global hosts allow/ hosts
+            deny parameters before doing any parsing of the packet
+            passed to us by the client.  This prevents attacks on our
+            parsing code from hosts not in the hosts allow list */
+         if (!check_access(-1)) {
+                 /* send a negative session response "not listining on calling
+                  name" */
+                 static unsigned char buf[5] = {0x83, 0, 0, 1, 0x81};
+                 DEBUG(1,("%s Connection denied from %s\n",
+                          timestring(),client_addr()));
+                 send_smb(Client,(char *)buf);
+                 exit_server("connection denied");
+         }
+  }
+
   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));
 
@@ -2512,8 +2504,12 @@ should be %d).\n", msg_len, OPLOCK_BREAK_MSG_LEN));
         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 timeval tval;
         struct sockaddr_in toaddr;
 
+        tval.tv_sec = IVAL(msg_start, OPLOCK_BREAK_SEC_OFFSET);
+        tval.tv_usec = IVAL(msg_start, OPLOCK_BREAK_USEC_OFFSET);
+
         DEBUG(5,("process_local_message: oplock break request from \
 pid %d, port %d, dev = %x, inode = %x\n", remotepid, from_port, dev, inode));
 
@@ -2526,7 +2522,7 @@ pid %d, port %d, dev = %x, inode = %x\n", remotepid, from_port, dev, inode));
 
         if(global_oplocks_open != 0)
         {
-          if(oplock_break(dev, inode) == False)
+          if(oplock_break(dev, inode, &tval) == False)
           {
             DEBUG(0,("process_local_message: oplock break failed - \
 not returning udp message.\n"));
@@ -2572,17 +2568,19 @@ pid %d, port %d, for file dev = %x, inode = %x\n", remotepid,
 /****************************************************************************
  Process an oplock break directly.
 ****************************************************************************/
-BOOL oplock_break(uint32 dev, uint32 inode)
+BOOL oplock_break(uint32 dev, uint32 inode, struct timeval *tval)
 {
   extern int Client;
   static char *inbuf = NULL;
   static char *outbuf = NULL;
   files_struct *fsp = NULL;
   int fnum;
-  share_lock_token token;
   time_t start_time;
   BOOL shutdown_server = False;
 
+  DEBUG(5,("oplock_break: called for dev = %x, inode = %x. Current \
+global_oplocks_open = %d\n", dev, inode, global_oplocks_open));
+
   if(inbuf == NULL)
   {
     inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
@@ -2607,7 +2605,9 @@ BOOL oplock_break(uint32 dev, uint32 inode)
     if(OPEN_FNUM(fnum))
     {
       fsp = &Files[fnum];
-      if((fsp->fd_ptr->dev == dev) && (fsp->fd_ptr->inode == inode))
+      if((fsp->fd_ptr->dev == dev) && (fsp->fd_ptr->inode == inode) &&
+         (fsp->open_time.tv_sec == tval->tv_sec) && 
+         (fsp->open_time.tv_usec == tval->tv_usec))
         break;
     }
   }
@@ -2622,16 +2622,18 @@ allowing break to succeed.\n", dev, inode, fnum));
 
   /* 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).
+  /* There is a potential race condition in that an oplock could
+     have been broken due to another udp request, and yet there are
+     still oplock break messages being sent in the udp message
+     queue for this file. So return true if we don't have an oplock,
+     as we may have just freed it.
    */
 
   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;
+    DEBUG(3,("oplock_break: file %s (fnum = %d, dev = %x, inode = %x) has no oplock. \
+Allowing break to succeed regardless.\n", fsp->name, fnum, dev, inode));
+    return True;
   }
 
   /* Now comes the horrid part. We must send an oplock break to the client,
@@ -2691,7 +2693,8 @@ inode = %x).\n", fsp->name, fnum, dev, inode));
     process_smb(inbuf, outbuf);
 
     /* We only need this in case a readraw crossed on the wire. */
-    global_oplock_break = False;
+    if(global_oplock_break)
+      global_oplock_break = False;
 
     /*
      * Die if we go over the time limit.
@@ -2722,16 +2725,10 @@ inode = %x).\n", fsp->name, fnum, dev, inode));
 
   if(OPEN_FNUM(fnum))
   {
-    /* Remove the oplock flag from the sharemode. */
-    lock_share_entry(fsp->cnum, dev, inode, &token);
-    if(remove_share_oplock( fnum, token)==False)
-    {
-      DEBUG(0,("oplock_break: failed to remove share oplock for fnum %d, \
-dev = %x, inode = %x\n", fnum, dev, inode));
-      unlock_share_entry(fsp->cnum, dev, inode, token);
-      return False;
-    }
-    unlock_share_entry(fsp->cnum, dev, inode, token);
+    /* The lockingX reply will have removed the oplock flag 
+       from the sharemode. */
+    /* Paranoia.... */
+    fsp->granted_oplock = False;
   }
 
   global_oplocks_open--;
@@ -2744,6 +2741,9 @@ dev = %x, inode = %x\n", fnum, dev, inode));
     abort();
   }
 
+  DEBUG(5,("oplock_break: returning success for fnum = %d, dev = %x, inode = %x. Current \
+global_oplocks_open = %d\n", fnum, dev, inode, global_oplocks_open));
+
   return True;
 }
 
@@ -2764,12 +2764,15 @@ BOOL request_oplock_break(min_share_mode_entry *share_entry,
     /* 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 \
+      DEBUG(0,("request_oplock_break: corrupt share mode entry - pid = %d, port = %d \
 should be %d\n", pid, share_entry->op_port, oplock_port));
       return False;
     }
+
+    DEBUG(5,("request_oplock_break: breaking our own oplock\n"));
+
     /* Call oplock break direct. */
-    return oplock_break(dev, inode);
+    return oplock_break(dev, inode, &share_entry->time);
   }
 
   /* We need to send a OPLOCK_BREAK_CMD message to the
@@ -2779,6 +2782,8 @@ should be %d\n", pid, share_entry->op_port, oplock_port));
   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);
+  SIVAL(op_break_msg,OPLOCK_BREAK_SEC_OFFSET,(uint32)share_entry->time.tv_sec);
+  SIVAL(op_break_msg,OPLOCK_BREAK_USEC_OFFSET,(uint32)share_entry->time.tv_usec);
 
   /* set the address and port */
   bzero((char *)&addr_out,sizeof(addr_out));