92abce8d8fcecabb96d46d3834da79fe5c5a33fb
[samba.git] / source3 / utils / smbpasswd.c
1 #ifdef SMB_PASSWD
2
3 /*
4  * Unix SMB/Netbios implementation. Version 1.9. smbpasswd module. Copyright
5  * (C) Jeremy Allison 1995-1997.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  * 
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  * 
17  * You should have received a copy of the GNU General Public License along with
18  * this program; if not, write to the Free Software Foundation, Inc., 675
19  * Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "includes.h"
23 #include "des.h"
24
25 /* Static buffers we will return. */
26 static struct smb_passwd pw_buf;
27 static pstring  user_name;
28 static unsigned char smbpwd[16];
29 static unsigned char smbntpwd[16];
30
31 static int gethexpwd(char *p, char *pwd)
32 {
33         int i;
34         unsigned char   lonybble, hinybble;
35         char           *hexchars = "0123456789ABCDEF";
36         char           *p1, *p2;
37         for (i = 0; i < 32; i += 2) {
38                 hinybble = toupper(p[i]);
39                 lonybble = toupper(p[i + 1]);
40
41                 p1 = strchr(hexchars, hinybble);
42                 p2 = strchr(hexchars, lonybble);
43                 if (!p1 || !p2)
44                         return (False);
45
46                 hinybble = PTR_DIFF(p1, hexchars);
47                 lonybble = PTR_DIFF(p2, hexchars);
48
49                 pwd[i / 2] = (hinybble << 4) | lonybble;
50         }
51         return (True);
52 }
53
54 struct smb_passwd *
55 _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd, 
56                 BOOL *got_valid_nt_entry, long *pwd_seekpos)
57 {
58         char            linebuf[256];
59         unsigned char   c;
60         unsigned char  *p;
61         long            uidval;
62         long            linebuf_len;
63
64         /*
65          * Scan the file, a line at a time and check if the name matches.
66          */
67         while (!feof(fp)) {
68                 linebuf[0] = '\0';
69                 *pwd_seekpos = ftell(fp);
70
71                 fgets(linebuf, 256, fp);
72                 if (ferror(fp))
73                         return NULL;
74
75                 /*
76                  * Check if the string is terminated with a newline - if not
77                  * then we must keep reading and discard until we get one.
78                  */
79                 linebuf_len = strlen(linebuf);
80                 if (linebuf[linebuf_len - 1] != '\n') {
81                         c = '\0';
82                         while (!ferror(fp) && !feof(fp)) {
83                                 c = fgetc(fp);
84                                 if (c == '\n')
85                                         break;
86                         }
87                 } else
88                         linebuf[linebuf_len - 1] = '\0';
89
90                 if ((linebuf[0] == 0) && feof(fp))
91                         break;
92                 /*
93                  * The line we have should be of the form :-
94                  * 
95                  * username:uid:[32hex bytes]:....other flags presently
96                  * ignored....
97                  * 
98                  * or,
99                  * 
100                  * username:uid:[32hex bytes]:[32hex bytes]:....ignored....
101                  * 
102                  * if Windows NT compatible passwords are also present.
103                  */
104
105                 if (linebuf[0] == '#' || linebuf[0] == '\0')
106                         continue;
107                 p = (unsigned char *) strchr(linebuf, ':');
108                 if (p == NULL)
109                         continue;
110                 /*
111                  * As 256 is shorter than a pstring we don't need to check
112                  * length here - if this ever changes....
113                  */
114                 strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
115                 user_name[PTR_DIFF(p, linebuf)] = '\0';
116                 if (!strequal(user_name, name))
117                         continue;
118
119                 /* User name matches - get uid and password */
120                 p++;            /* Go past ':' */
121                 if (!isdigit(*p))
122                         return (False);
123
124                 uidval = atoi((char *) p);
125                 while (*p && isdigit(*p))
126                         p++;
127
128                 if (*p != ':')
129                         return (False);
130
131                 /*
132                  * Now get the password value - this should be 32 hex digits
133                  * which are the ascii representations of a 16 byte string.
134                  * Get two at a time and put them into the password.
135                  */
136                 p++;
137                 *pwd_seekpos += PTR_DIFF(p, linebuf);   /* Save exact position
138                                                          * of passwd in file -
139                                                          * this is used by
140                                                          * smbpasswd.c */
141                 if (*p == '*' || *p == 'X') {
142                         /* Password deliberately invalid - end here. */
143                         *valid_old_pwd = False;
144                         *got_valid_nt_entry = False;
145                         pw_buf.smb_nt_passwd = NULL;    /* No NT password (yet)*/
146
147                         /* Now check if the NT compatible password is
148                            available. */
149                         p += 33; /* Move to the first character of the line after 
150                                                 the lanman password. */
151                         if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
152                                 /* NT Entry was valid - even if 'X' or '*', can be overwritten */
153                                 *got_valid_nt_entry = True;
154                                 if (*p != '*' && *p != 'X') {
155                                   if (gethexpwd((char *)p,(char *)smbntpwd))
156                                     pw_buf.smb_nt_passwd = smbntpwd;
157                                 }
158                         }
159                         pw_buf.smb_name = user_name;
160                         pw_buf.smb_userid = uidval;
161                         pw_buf.smb_passwd = NULL;       /* No password */
162                         return (&pw_buf);
163                 }
164                 if (linebuf_len < (PTR_DIFF(p, linebuf) + 33))
165                         return (False);
166
167                 if (p[32] != ':')
168                         return (False);
169
170                 if (!strncasecmp((char *)p, "NO PASSWORD", 11)) {
171                   pw_buf.smb_passwd = NULL;     /* No password */
172                 } else {
173                   if(!gethexpwd((char *)p,(char *)smbpwd))
174                     return False;
175                   pw_buf.smb_passwd = smbpwd;
176                 }
177
178                 pw_buf.smb_name = user_name;
179                 pw_buf.smb_userid = uidval;
180                 pw_buf.smb_nt_passwd = NULL;
181                 *got_valid_nt_entry = False;
182                 *valid_old_pwd = True;
183
184                 /* Now check if the NT compatible password is
185                    available. */
186                 p += 33; /* Move to the first character of the line after 
187                                         the lanman password. */
188                 if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
189                         /* NT Entry was valid - even if 'X' or '*', can be overwritten */
190                         *got_valid_nt_entry = True;
191                         if (*p != '*' && *p != 'X') {
192                           if (gethexpwd((char *)p,(char *)smbntpwd))
193                             pw_buf.smb_nt_passwd = smbntpwd;
194                         }
195                 }
196                 return &pw_buf;
197         }
198         return NULL;
199 }
200
201 /*
202  * Print command usage on stderr and die.
203  */
204 static void usage(char *name)
205 {
206         fprintf(stderr, "Usage is : %s [-add] [username] [password]\n", name);
207         exit(1);
208 }
209
210  int main(int argc, char **argv)
211 {
212   int             real_uid;
213   struct passwd  *pwd;
214   fstring         old_passwd;
215   uchar           old_p16[16];
216   uchar           old_nt_p16[16];
217   fstring         new_passwd;
218   uchar           new_p16[16];
219   uchar           new_nt_p16[16];
220   char           *p;
221   struct smb_passwd *smb_pwent;
222   FILE           *fp;
223   BOOL            valid_old_pwd = False;
224   BOOL                  got_valid_nt_entry = False;
225   BOOL            add_user = False;
226   int             add_pass = 0;
227   long            seekpos;
228   int             pwfd;
229   char            ascii_p16[66];
230   char            c;
231   int             ret, i, err, writelen;
232   int             lockfd = -1;
233   char           *pfile = SMB_PASSWD_FILE;
234   char            readbuf[16 * 1024];
235   
236   TimeInit();
237
238   setup_logging(argv[0],True);
239   
240   charset_initialise();
241   
242 #ifndef DEBUG_PASSWORD
243   /* Check the effective uid */
244   if (geteuid() != 0) {
245     fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
246     exit(1);
247   }
248 #endif
249   
250   /* Get the real uid */
251   real_uid = getuid();
252   
253   /* Deal with usage problems */
254   if (real_uid == 0)
255   {
256     /* As root we can change anothers password and add a user. */
257     if (argc > 4 )
258       usage(argv[0]);
259   }
260   else if (argc == 2 || argc > 3)
261   {
262     fprintf(stderr, "%s: Only root can set anothers password.\n", argv[0]);
263     usage(argv[0]);
264   }
265   
266   if (real_uid == 0 && (argc > 1))
267   {
268     /* We are root - check if we should add the user */
269     if ((argv[1][0] == '-') && (argv[1][1] == 'a'))
270       add_user = True;
271
272     if(add_user && (argc <= 2 || argc > 4))
273       usage(argv[0]);
274
275     /* root can specify password on command-line */
276     if (argc == (add_user ? 4 : 3))
277     {
278       /* -a argument (add_user): new password is 3rd argument. */
279       /* no -a argument (add_user): new password is 2nd argument */
280
281       add_pass = add_user ? 3 : 2;
282     }
283
284     /* If we are root we can change another's password. */
285     strncpy(user_name, add_user ? argv[2] : argv[1], sizeof(user_name) - 1);
286     user_name[sizeof(user_name) - 1] = '\0';
287
288     pwd = getpwnam(user_name);
289   }
290   else
291   {
292     /* non-root can specify old pass / new pass on command-line */
293     if (argc == 3)
294     {
295        /* non-root specifies new password as 2nd argument */
296        add_pass = 2;
297     }
298
299     pwd = getpwuid(real_uid);
300   }
301   
302   if (pwd == 0) {
303     fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
304     exit(1);
305   }
306
307   /* If we are root we don't ask for the old password. */
308   old_passwd[0] = '\0';
309   if (real_uid != 0)
310   {
311     if (add_pass)
312     {
313       /* old password, as non-root, is 1st argument */
314       strncpy(old_passwd, argv[1], sizeof(fstring));
315     }
316     else
317     {
318       p = getpass("Old SMB password:");
319       strncpy(old_passwd, p, sizeof(fstring));
320     }
321     old_passwd[sizeof(fstring)-1] = '\0';
322   }
323
324   if (add_pass)
325   {
326     /* new password is specified on the command line */
327     strncpy(new_passwd, argv[add_user ? 3 : 2], sizeof(new_passwd) - 1);
328     new_passwd[sizeof(new_passwd) - 1] = '\0';
329   }
330   else
331   {
332     new_passwd[0] = '\0';
333
334     p = getpass("New SMB password:");
335
336     strncpy(new_passwd, p, sizeof(fstring));
337     new_passwd[sizeof(fstring)-1] = '\0';
338
339     p = getpass("Retype new SMB password:");
340
341     if (strncmp(p, new_passwd, sizeof(fstring)-1))
342     {
343       fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
344       exit(1);
345     }
346   }
347   
348   if (new_passwd[0] == '\0')
349   {
350     printf("Password not set\n");
351     exit(0);
352   }
353   
354   /* Calculate the MD4 hash (NT compatible) of the old and new passwords */
355   memset(old_nt_p16, '\0', 16);
356   E_md4hash((uchar *)old_passwd, old_nt_p16);
357   
358   memset(new_nt_p16, '\0', 16);
359   E_md4hash((uchar *) new_passwd, new_nt_p16);
360   
361   /* Mangle the passwords into Lanman format */
362   old_passwd[14] = '\0';
363   strupper(old_passwd);
364   new_passwd[14] = '\0';
365   strupper(new_passwd);
366   
367   /*
368    * Calculate the SMB (lanman) hash functions of both old and new passwords.
369    */
370   
371   memset(old_p16, '\0', 16);
372   E_P16((uchar *) old_passwd, old_p16);
373   
374   memset(new_p16, '\0', 16);
375   E_P16((uchar *) new_passwd, new_p16);
376   
377   /*
378    * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
379    * filename
380    */
381   if ((fp = fopen(pfile, "r+")) == NULL) {
382     err = errno;
383     fprintf(stderr, "%s: Failed to open password file %s.\n",
384             argv[0], pfile);
385     errno = err;
386     perror(argv[0]);
387     exit(err);
388   }
389   /* Set read buffer to 16k for effiecient reads */
390   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
391   
392   /* make sure it is only rw by the owner */
393   chmod(pfile, 0600);
394   
395   /* Lock the smbpasswd file for write. */
396   if ((lockfd = pw_file_lock(pfile, F_WRLCK, 5)) < 0) {
397     err = errno;
398     fprintf(stderr, "%s: Failed to lock password file %s.\n",
399             argv[0], pfile);
400     fclose(fp);
401     errno = err;
402     perror(argv[0]);
403     exit(err);
404   }
405   /* Get the smb passwd entry for this user */
406   smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd, 
407                                &got_valid_nt_entry, &seekpos);
408   if (smb_pwent == NULL) {
409     if(add_user == False) {
410       fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
411               argv[0], pwd->pw_name, pfile);
412       fclose(fp);
413       pw_file_unlock(lockfd);
414       exit(1);
415     }
416
417     /* Create a new smb passwd entry and set it to the given password. */
418     {
419       int fd;
420       int new_entry_length;
421       char *new_entry;
422       long offpos;
423
424       /* The add user write needs to be atomic - so get the fd from 
425          the fp and do a raw write() call.
426        */
427       fd = fileno(fp);
428
429       if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
430         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
431 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
432         fclose(fp);
433         pw_file_unlock(lockfd);
434         exit(1);
435       }
436
437       new_entry_length = strlen(pwd->pw_name) + 1 + 15 + 1 + 
438                          32 + 1 + 32 + 1 + strlen(pwd->pw_gecos) + 
439                          1 + strlen(pwd->pw_dir) + 1 + 
440                          strlen(pwd->pw_shell) + 1;
441       if((new_entry = (char *)malloc( new_entry_length )) == 0) {
442         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
443 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
444         fclose(fp);
445         pw_file_unlock(lockfd);
446         exit(1);
447       }
448
449       sprintf(new_entry, "%s:%u:", pwd->pw_name, pwd->pw_uid);
450       p = &new_entry[strlen(new_entry)];
451       for( i = 0; i < 16; i++)
452         sprintf(&p[i*2], "%02X", new_p16[i]);
453       p += 32;
454       *p++ = ':';
455       for( i = 0; i < 16; i++)
456         sprintf(&p[i*2], "%02X", new_nt_p16[i]);
457       p += 32;
458       *p++ = ':';
459       sprintf(p, "%s:%s:%s\n", pwd->pw_gecos, 
460               pwd->pw_dir, pwd->pw_shell);
461       if(write(fd, new_entry, strlen(new_entry)) != strlen(new_entry)) {
462         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
463 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
464         /* Remove the entry we just wrote. */
465         if(ftruncate(fd, offpos) == -1) {
466           fprintf(stderr, "%s: ERROR failed to ftruncate file %s. \
467 Error was %d. Password file may be corrupt ! Please examine by hand !\n", 
468                    argv[0], pwd->pw_name, errno);
469         }
470         fclose(fp);
471         pw_file_unlock(lockfd);
472         exit(1);
473       }
474       
475       fclose(fp);  
476       pw_file_unlock(lockfd);  
477       exit(0);
478     }
479   }
480
481   /* If we are root or the password is 'NO PASSWORD' then
482      we don't need to check the old password. */
483   if (real_uid != 0) {
484     if (valid_old_pwd == False) {
485       fprintf(stderr, "%s: User %s has no old SMB password.\n", argv[0], pwd->pw_name);
486     }
487     /* Check the old Lanman password - NULL means 'NO PASSWORD' */
488     if (smb_pwent->smb_passwd != NULL) {
489       if (memcmp(old_p16, smb_pwent->smb_passwd, 16)) {
490         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
491         fclose(fp);
492         pw_file_unlock(lockfd);
493         exit(1);
494       }
495     }
496     /* Check the NT password if it exists */
497     if (smb_pwent->smb_nt_passwd != NULL) {
498       if (memcmp(old_nt_p16, smb_pwent->smb_nt_passwd, 16)) {
499         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
500         fclose(fp);
501         pw_file_unlock(lockfd);
502         exit(1);
503       }
504     }
505   }
506   /*
507    * If we get here either we were root or the old password checked out
508    * ok.
509    */
510   /* Create the 32 byte representation of the new p16 */
511   for (i = 0; i < 16; i++) {
512     sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
513   }
514   if(got_valid_nt_entry) {
515     /* Add on the NT md4 hash */
516     ascii_p16[32] = ':';
517     for (i = 0; i < 16; i++) {
518       sprintf(&ascii_p16[(i * 2)+33], "%02X", (uchar) new_nt_p16[i]);
519     }
520   }
521   /*
522    * Do an atomic write into the file at the position defined by
523    * seekpos.
524    */
525   pwfd = fileno(fp);
526   ret = lseek(pwfd, seekpos - 1, SEEK_SET);
527   if (ret != seekpos - 1) {
528     err = errno;
529     fprintf(stderr, "%s: seek fail on file %s.\n",
530             argv[0], pfile);
531     fclose(fp);
532     errno = err;
533     perror(argv[0]);
534     pw_file_unlock(lockfd);
535     exit(1);
536   }
537   /* Sanity check - ensure the character is a ':' */
538   if (read(pwfd, &c, 1) != 1) {
539     err = errno;
540     fprintf(stderr, "%s: read fail on file %s.\n",
541             argv[0], pfile);
542     fclose(fp);
543     errno = err;
544     perror(argv[0]);
545     pw_file_unlock(lockfd);
546     exit(1);
547   }
548   if (c != ':') {
549     fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
550             argv[0], pfile);
551     fclose(fp);
552     pw_file_unlock(lockfd);
553     exit(1);
554   }
555   writelen = (got_valid_nt_entry) ? 65 : 32;
556   if (write(pwfd, ascii_p16, writelen) != writelen) {
557     err = errno;
558     fprintf(stderr, "%s: write fail in file %s.\n",
559             argv[0], pfile);
560     fclose(fp);
561     errno = err;
562     perror(argv[0]);
563     pw_file_unlock(lockfd);
564     exit(err);
565   }
566   fclose(fp);
567   pw_file_unlock(lockfd);
568   printf("Password changed\n");
569   return 0;
570 }
571
572 #else
573
574 #include "includes.h"
575
576 int 
577 main(int argc, char **argv)
578 {
579   printf("smb password encryption not selected in Makefile\n");
580   return 0;
581 }
582 #endif