r23784: use the GPLv3 boilerplate as recommended by the FSF and the license text
[abartlet/samba.git/.git] / source / lib / genrand.c
index c36cdd4b8c354b283e398ed4911fb6d49b3e8d6a..a08fe6719f20127be2b18f9b062c648f77827859 100644 (file)
@@ -1,14 +1,13 @@
 /* 
-   Unix SMB/Netbios implementation.
-   Version 1.9.
+   Unix SMB/CIFS implementation.
 
    Functions to create reasonable random numbers for crypto use.
 
-   Copyright (C) Jeremy Allison 1998
+   Copyright (C) Jeremy Allison 2001
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
 
-extern int DEBUGLEVEL;
-static uint32 counter = 0;
+static unsigned char smb_arc4_state[258];
+static uint32 counter;
+
+static BOOL done_reseed = False;
+static void (*reseed_callback)(int *newseed);
+
+/**************************************************************** 
+ Copy any user given reseed data.
+*****************************************************************/
+
+void set_rand_reseed_callback(void (*fn)(int *))
+{
+       reseed_callback = fn;
+       set_need_random_reseed();
+}
+
+void set_need_random_reseed(void)
+{
+       done_reseed = False;
+}
+
+static void get_rand_reseed_data(int *reseed_data)
+{
+       if (reseed_callback) {
+               reseed_callback(reseed_data);
+       } else {
+               *reseed_data = 0;
+       }
+}
 
 /****************************************************************
-get a 16 byte hash from the contents of a file
-Note that the hash is not initialised.
+ Get a 16 byte hash from the contents of a file.
+ Note that the hash is not initialised.
 *****************************************************************/
-static void do_filehash(char *fname, unsigned char *hash)
+
+static void do_filehash(const char *fname, unsigned char *the_hash)
 {
        unsigned char buf[1011]; /* deliberate weird size */
        unsigned char tmp_md4[16];
        int fd, n;
 
-       fd = open(fname,O_RDONLY);
-       if (fd == -1) return;
+       fd = sys_open(fname,O_RDONLY,0);
+       if (fd == -1)
+               return;
 
        while ((n = read(fd, (char *)buf, sizeof(buf))) > 0) {
                mdfour(tmp_md4, buf, n);
                for (n=0;n<16;n++)
-                       hash[n] ^= tmp_md4[n];
+                       the_hash[n] ^= tmp_md4[n];
        }
        close(fd);
 }
 
+/**************************************************************
+ Try and get a good random number seed. Try a number of
+ different factors. Firstly, try /dev/urandom - use if exists.
 
+ We use /dev/urandom as a read of /dev/random can block if
+ the entropy pool dries up. This leads clients to timeout
+ or be very slow on connect.
 
-/****************************************************************
- Try and get a seed by looking at the atimes of files in a given
- directory. XOR them into the buf array.
-*****************************************************************/
+ If we can't use /dev/urandom then seed the stream random generator
+ above...
+**************************************************************/
 
-static void do_dirrand(char *name, unsigned char *buf, int buf_len)
+static int do_reseed(BOOL use_fd, int fd)
 {
-  void *dp = dos_opendir(name);
-  pstring fullname;
-  int len_left;
-  int fullname_len;
-  char *pos;
-
-  pstrcpy(fullname, name);
-  fullname_len = strlen(fullname);
-
-  if(fullname_len + 2 > sizeof(pstring))
-    return;
-
-  if(fullname[fullname_len] != '/') {
-    fullname[fullname_len] = '/';
-    fullname[fullname_len+1] = '\0';
-    fullname_len = strlen(fullname);
-  }
-
-  len_left = sizeof(pstring) - fullname_len - 1;
-  pos = &fullname[fullname_len];
-
-  if(dp != NULL) {
-    char *p;
-
-    while ((p = readdirname(dp))) {           
-      SMB_STRUCT_STAT st;
-
-      if(strlen(p) <= len_left)
-        pstrcpy(pos, p);
-
-      if(dos_stat(fullname,&st) == 0) {
-        SIVAL(buf, ((counter * 4)%(buf_len-4)), 
-              IVAL(buf,((counter * 4)%(buf_len-4))) ^ st.st_atime);
-        counter++;
-        DEBUG(10,("do_dirrand: value from file %s.\n", fullname));
-      }
-    }
-    closedir(dp); 
-  }
+       unsigned char seed_inbuf[40];
+       uint32 v1, v2; struct timeval tval; pid_t mypid;
+       struct passwd *pw;
+       int reseed_data = 0;
+
+       if (use_fd) {
+               if (fd != -1)
+                       return fd;
+
+               fd = sys_open( "/dev/urandom", O_RDONLY,0);
+               if(fd >= 0)
+                       return fd;
+       }
+
+       /* Add in some secret file contents */
+
+       do_filehash("/etc/shadow", &seed_inbuf[0]);
+       do_filehash(lp_smb_passwd_file(), &seed_inbuf[16]);
+
+       /*
+        * Add in the root encrypted password.
+        * On any system where security is taken
+        * seriously this will be secret.
+        */
+
+       pw = getpwnam_alloc(NULL, "root");
+       if (pw && pw->pw_passwd) {
+               size_t i;
+               unsigned char md4_tmp[16];
+               mdfour(md4_tmp, (unsigned char *)pw->pw_passwd, strlen(pw->pw_passwd));
+               for (i=0;i<16;i++)
+                       seed_inbuf[8+i] ^= md4_tmp[i];
+               TALLOC_FREE(pw);
+       }
+
+       /*
+        * Add the counter, time of day, and pid.
+        */
+
+       GetTimeOfDay(&tval);
+       mypid = sys_getpid();
+       v1 = (counter++) + mypid + tval.tv_sec;
+       v2 = (counter++) * mypid + tval.tv_usec;
+
+       SIVAL(seed_inbuf, 32, v1 ^ IVAL(seed_inbuf, 32));
+       SIVAL(seed_inbuf, 36, v2 ^ IVAL(seed_inbuf, 36));
+
+       /*
+        * Add any user-given reseed data.
+        */
+
+       get_rand_reseed_data(&reseed_data);
+       if (reseed_data) {
+               size_t i;
+               for (i = 0; i < sizeof(seed_inbuf); i++)
+                       seed_inbuf[i] ^= ((char *)(&reseed_data))[i % sizeof(reseed_data)];
+       }
+
+       smb_arc4_init(smb_arc4_state, seed_inbuf, sizeof(seed_inbuf));
+
+       return -1;
 }
 
-/**************************************************************
- Try and get a good random number seed. Try a number of
- different factors. Firstly, try /dev/random and try and
- read from this. If this fails iterate through /tmp and
- /dev and XOR all the file timestamps. Next add in
- a hash of the contents of /etc/shadow and the smb passwd
- file and a combination of pid and time of day (yes I know this
- sucks :-). Finally md4 the result.
-
- The result goes in a 16 byte buffer passed from the caller
-**************************************************************/
+/*******************************************************************
+ Interface to the (hopefully) good crypto random number generator.
+********************************************************************/
 
-static uint32 do_reseed(unsigned char *md4_outbuf)
+void generate_random_buffer( unsigned char *out, int len)
 {
-  unsigned char md4_inbuf[40];
-  BOOL got_random = False;
-  uint32 v1, v2, ret;
-  int fd;
-  struct timeval tval;
-  pid_t mypid;
-  struct passwd *pw;
-
-  memset(md4_inbuf, '\0', sizeof(md4_inbuf));
-
-  fd = open( "/dev/random", O_RDONLY);
-  if(fd >= 0) {
-    /* 
-     * We can use /dev/random !
-     */
-    if(read(fd, md4_inbuf, 40) == 40) {
-      got_random = True;
-      DEBUG(10,("do_reseed: got 40 bytes from /dev/random.\n"));
-    }
-    close(fd);
-  }
-
-  if(!got_random) {
-    /*
-     * /dev/random failed - try /tmp and /dev for timestamps.
-     */
-    do_dirrand("/tmp", md4_inbuf, sizeof(md4_inbuf));
-    do_dirrand("/dev", md4_inbuf, sizeof(md4_inbuf));
-  }
-
-  /* possibly add in some secret file contents */
-  do_filehash("/etc/shadow", &md4_inbuf[0]);
-  do_filehash(lp_smb_passwd_file(), &md4_inbuf[16]);
-
-  /* add in the root encrypted password. On any system where security is taken
-     seriously this will be secret */
-  pw = getpwnam("root");
-  if (pw && pw->pw_passwd) {
-         int i;
-         unsigned char md4_tmp[16];
-         mdfour(md4_tmp, (unsigned char *)pw->pw_passwd, strlen(pw->pw_passwd));
-         for (i=0;i<16;i++)
-                 md4_inbuf[8+i] ^= md4_tmp[i];
-  }
-
-  /*
-   * Finally add the counter, time of day, and pid.
-   */
-  GetTimeOfDay(&tval);
-  mypid = getpid();
-  v1 = (counter++) + mypid + tval.tv_sec;
-  v2 = (counter++) * mypid + tval.tv_usec;
-
-  SIVAL(md4_inbuf, 32, v1 ^ IVAL(md4_inbuf, 32));
-  SIVAL(md4_inbuf, 36, v2 ^ IVAL(md4_inbuf, 36));
-
-  mdfour(md4_outbuf, md4_inbuf, sizeof(md4_inbuf));
-
-  /*
-   * Return a 32 bit int created from XORing the
-   * 16 bit return buffer.
-   */
-
-  ret = IVAL(md4_outbuf, 0);
-  ret ^= IVAL(md4_outbuf, 4);
-  ret ^= IVAL(md4_outbuf, 8);
-  return (ret ^ IVAL(md4_outbuf, 12));
+       static int urand_fd = -1;
+       unsigned char md4_buf[64];
+       unsigned char tmp_buf[16];
+       unsigned char *p;
+
+       if(!done_reseed) {
+               urand_fd = do_reseed(True, urand_fd);
+               done_reseed = True;
+       }
+
+       if (urand_fd != -1 && len > 0) {
+
+               if (read(urand_fd, out, len) == len)
+                       return; /* len bytes of random data read from urandom. */
+
+               /* Read of urand error, drop back to non urand method. */
+               close(urand_fd);
+               urand_fd = -1;
+               do_reseed(False, -1);
+               done_reseed = True;
+       }
+
+       /*
+        * Generate random numbers in chunks of 64 bytes,
+        * then md4 them & copy to the output buffer.
+        * This way the raw state of the stream is never externally
+        * seen.
+        */
+
+       p = out;
+       while(len > 0) {
+               int copy_len = len > 16 ? 16 : len;
+
+               smb_arc4_crypt(smb_arc4_state, md4_buf, sizeof(md4_buf));
+               mdfour(tmp_buf, md4_buf, sizeof(md4_buf));
+               memcpy(p, tmp_buf, copy_len);
+               p += copy_len;
+               len -= copy_len;
+       }
 }
 
 /*******************************************************************
Interface to the (hopefully) good crypto random number generator.
Use the random number generator to generate a random string.
 ********************************************************************/
 
-void generate_random_buffer( unsigned char *out, int len, BOOL re_seed)
+static char c_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_-#.,";
+
+char *generate_random_str(size_t len)
 {
-  static BOOL done_reseed = False;
-  static unsigned char md4_buf[16];
-  unsigned char tmp_buf[16];
-  unsigned char *p;
-
-  if(!done_reseed || re_seed) {
-         srandom(do_reseed(md4_buf));
-         done_reseed = True;
-  }
-
-  /*
-   * Generate random numbers in chunks of 64 bytes,
-   * then md4 them & copy to the output buffer.
-   * Added XOR with output from random, seeded
-   * by the original md4_buf. This is to stop the
-   * output from this function being the previous
-   * md4_buf md4'ed. The output from this function
-   * is often output onto the wire, and so it should
-   * not be possible to guess the next output from
-   * this function based on the previous output.
-   * XORing in the output from random(), seeded by
-   * the original md4 hash should stop this. JRA.
-   */
-
-  p = out;
-  while(len > 0) {
-    int i;
-    int copy_len = len > 16 ? 16 : len;
-    mdfour(tmp_buf, md4_buf, sizeof(md4_buf));
-    memcpy(md4_buf, tmp_buf, sizeof(md4_buf));
-    /* XOR in output from random(). */
-    for(i = 0; i < 4; i++)
-      SIVAL(tmp_buf, i*4, (IVAL(tmp_buf, i*4) ^ (uint32)random()));
-    memcpy(p, tmp_buf, copy_len);
-    p += copy_len;
-    len -= copy_len;
-  }
+       static unsigned char retstr[256];
+       size_t i;
+
+       memset(retstr, '\0', sizeof(retstr));
+
+       if (len > sizeof(retstr)-1)
+               len = sizeof(retstr) -1;
+       generate_random_buffer( retstr, len);
+       for (i = 0; i < len; i++)
+               retstr[i] = c_list[ retstr[i] % (sizeof(c_list)-1) ];
+
+       retstr[i] = '\0';
+
+       return (char *)retstr;
 }