updated smbpasswd to take the password on the command-line.
[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 (as root) is : %s [-add] [username] [password]\n", name);
207         fprintf(stderr, "Usage (as user) is : %s [old password  new password]\n", name);
208         exit(1);
209 }
210
211  int main(int argc, char **argv)
212 {
213   int             real_uid;
214   struct passwd  *pwd;
215   fstring         old_passwd;
216   uchar           old_p16[16];
217   uchar           old_nt_p16[16];
218   fstring         new_passwd;
219   uchar           new_p16[16];
220   uchar           new_nt_p16[16];
221   char           *p;
222   struct smb_passwd *smb_pwent;
223   FILE           *fp;
224   BOOL            valid_old_pwd = False;
225   BOOL                  got_valid_nt_entry = False;
226   BOOL            add_user = False;
227   int             add_pass = 0;
228   long            seekpos;
229   int             pwfd;
230   char            ascii_p16[66];
231   char            c;
232   int             ret, i, err, writelen;
233   int             lockfd = -1;
234   char           *pfile = SMB_PASSWD_FILE;
235   char            readbuf[16 * 1024];
236   
237   TimeInit();
238
239   setup_logging(argv[0],True);
240   
241   charset_initialise();
242   
243 #ifndef DEBUG_PASSWORD
244   /* Check the effective uid */
245   if (geteuid() != 0) {
246     fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
247     exit(1);
248   }
249 #endif
250   
251   /* Get the real uid */
252   real_uid = getuid();
253   
254   /* Deal with usage problems */
255   if (real_uid == 0)
256   {
257     /* As root we can change anothers password and add a user. */
258     if (argc > 4 )
259       usage(argv[0]);
260   }
261   else if (argc == 2 || argc > 3)
262   {
263     fprintf(stderr, "%s: Only root can set anothers password.\n", argv[0]);
264     usage(argv[0]);
265   }
266   
267   if (real_uid == 0 && (argc > 1))
268   {
269     /* We are root - check if we should add the user */
270     if ((argv[1][0] == '-') && (argv[1][1] == 'a'))
271       add_user = True;
272
273     if(add_user && (argc != 4 || argc != 3))
274       usage(argv[0]);
275
276     /* root can specify password on command-line */
277     if (argc == (add_user ? 4 : 3))
278     {
279       /* -a argument (add_user): new password is 3rd argument. */
280       /* no -a argument (add_user): new password is 2nd argument */
281
282       add_pass = add_user ? 3 : 2;
283     }
284
285     /* If we are root we can change another's password. */
286     strncpy(user_name, add_user ? argv[2] : argv[1], sizeof(user_name) - 1);
287     user_name[sizeof(user_name) - 1] = '\0';
288
289     pwd = getpwnam(user_name);
290   }
291   else
292   {
293     /* non-root can specify old pass / new pass on command-line */
294     if (argc == 3)
295     {
296        /* non-root specifies new password as 2nd argument */
297        add_pass = 2;
298     }
299
300     pwd = getpwuid(real_uid);
301   }
302   
303   if (pwd == 0) {
304     fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
305     exit(1);
306   }
307
308   /* If we are root we don't ask for the old password. */
309   old_passwd[0] = '\0';
310   if (real_uid != 0)
311   {
312     if (add_pass)
313     {
314       /* old password, as non-root, is 1st argument */
315       strncpy(old_passwd, argv[1], sizeof(fstring));
316     }
317     else
318     {
319       p = getpass("Old SMB password:");
320       strncpy(old_passwd, p, sizeof(fstring));
321     }
322     old_passwd[sizeof(fstring)-1] = '\0';
323   }
324
325   if (add_pass)
326   {
327     /* new password is specified on the command line */
328     strncpy(new_passwd, argv[add_user ? 3 : 2], sizeof(new_passwd) - 1);
329     new_passwd[sizeof(new_passwd) - 1] = '\0';
330   }
331   else
332   {
333     new_passwd[0] = '\0';
334
335     p = getpass("New SMB password:");
336
337     strncpy(new_passwd, p, sizeof(fstring));
338     new_passwd[sizeof(fstring)-1] = '\0';
339
340     p = getpass("Retype new SMB password:");
341
342     if (strcmp(p, new_passwd))
343     {
344       fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
345       exit(1);
346     }
347   }
348   
349   if (new_passwd[0] == '\0')
350   {
351     printf("Password not set\n");
352     exit(0);
353   }
354   
355   /* Calculate the MD4 hash (NT compatible) of the old and new passwords */
356   memset(old_nt_p16, '\0', 16);
357   E_md4hash((uchar *)old_passwd, old_nt_p16);
358   
359   memset(new_nt_p16, '\0', 16);
360   E_md4hash((uchar *) new_passwd, new_nt_p16);
361   
362   /* Mangle the passwords into Lanman format */
363   old_passwd[14] = '\0';
364   strupper(old_passwd);
365   new_passwd[14] = '\0';
366   strupper(new_passwd);
367   
368   /*
369    * Calculate the SMB (lanman) hash functions of both old and new passwords.
370    */
371   
372   memset(old_p16, '\0', 16);
373   E_P16((uchar *) old_passwd, old_p16);
374   
375   memset(new_p16, '\0', 16);
376   E_P16((uchar *) new_passwd, new_p16);
377   
378   /*
379    * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
380    * filename
381    */
382   if ((fp = fopen(pfile, "r+")) == NULL) {
383     err = errno;
384     fprintf(stderr, "%s: Failed to open password file %s.\n",
385             argv[0], pfile);
386     errno = err;
387     perror(argv[0]);
388     exit(err);
389   }
390   /* Set read buffer to 16k for effiecient reads */
391   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
392   
393   /* make sure it is only rw by the owner */
394   chmod(pfile, 0600);
395   
396   /* Lock the smbpasswd file for write. */
397   if ((lockfd = pw_file_lock(pfile, F_WRLCK, 5)) < 0) {
398     err = errno;
399     fprintf(stderr, "%s: Failed to lock password file %s.\n",
400             argv[0], pfile);
401     fclose(fp);
402     errno = err;
403     perror(argv[0]);
404     exit(err);
405   }
406   /* Get the smb passwd entry for this user */
407   smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd, 
408                                &got_valid_nt_entry, &seekpos);
409   if (smb_pwent == NULL) {
410     if(add_user == False) {
411       fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
412               argv[0], pwd->pw_name, pfile);
413       fclose(fp);
414       pw_file_unlock(lockfd);
415       exit(1);
416     }
417
418     /* Create a new smb passwd entry and set it to the given password. */
419     {
420       int fd;
421       int i;
422       int new_entry_length;
423       char *new_entry;
424       char *p;
425       long offpos;
426
427       /* The add user write needs to be atomic - so get the fd from 
428          the fp and do a raw write() call.
429        */
430       fd = fileno(fp);
431
432       if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
433         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
434 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
435         fclose(fp);
436         pw_file_unlock(lockfd);
437         exit(1);
438       }
439
440       new_entry_length = strlen(pwd->pw_name) + 1 + 15 + 1 + 
441                          32 + 1 + 32 + 1 + strlen(pwd->pw_gecos) + 
442                          1 + strlen(pwd->pw_dir) + 1 + 
443                          strlen(pwd->pw_shell) + 1;
444       if((new_entry = (char *)malloc( new_entry_length )) == 0) {
445         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
446 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
447         fclose(fp);
448         pw_file_unlock(lockfd);
449         exit(1);
450       }
451
452       sprintf(new_entry, "%s:%u:", pwd->pw_name, pwd->pw_uid);
453       p = &new_entry[strlen(new_entry)];
454       for( i = 0; i < 16; i++)
455         sprintf(&p[i*2], "%02X", new_p16[i]);
456       p += 32;
457       *p++ = ':';
458       for( i = 0; i < 16; i++)
459         sprintf(&p[i*2], "%02X", new_nt_p16[i]);
460       p += 32;
461       *p++ = ':';
462       sprintf(p, "%s:%s:%s\n", pwd->pw_gecos, 
463               pwd->pw_dir, pwd->pw_shell);
464       if(write(fd, new_entry, strlen(new_entry)) != strlen(new_entry)) {
465         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
466 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
467         /* Remove the entry we just wrote. */
468         if(ftruncate(fd, offpos) == -1) {
469           fprintf(stderr, "%s: ERROR failed to ftruncate file %s. \
470 Error was %d. Password file may be corrupt ! Please examine by hand !\n", 
471                    argv[0], pwd->pw_name, errno);
472         }
473         fclose(fp);
474         pw_file_unlock(lockfd);
475         exit(1);
476       }
477       
478       fclose(fp);  
479       pw_file_unlock(lockfd);  
480       exit(0);
481     }
482   }
483
484   /* If we are root or the password is 'NO PASSWORD' then
485      we don't need to check the old password. */
486   if (real_uid != 0) {
487     if (valid_old_pwd == False) {
488       fprintf(stderr, "%s: User %s is disabled, plase contact your administrator to enable it.\n", argv[0], pwd->pw_name);
489       fclose(fp);
490       pw_file_unlock(lockfd);
491       exit(1);
492     }
493     /* Check the old Lanman password - NULL means 'NO PASSWORD' */
494     if (smb_pwent->smb_passwd != NULL) {
495       if (memcmp(old_p16, smb_pwent->smb_passwd, 16)) {
496         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
497         fclose(fp);
498         pw_file_unlock(lockfd);
499         exit(1);
500       }
501     }
502     /* Check the NT password if it exists */
503     if (smb_pwent->smb_nt_passwd != NULL) {
504       if (memcmp(old_nt_p16, smb_pwent->smb_nt_passwd, 16)) {
505         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
506         fclose(fp);
507         pw_file_unlock(lockfd);
508         exit(1);
509       }
510     }
511   }
512   /*
513    * If we get here either we were root or the old password checked out
514    * ok.
515    */
516   /* Create the 32 byte representation of the new p16 */
517   for (i = 0; i < 16; i++) {
518     sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
519   }
520   if(got_valid_nt_entry) {
521     /* Add on the NT md4 hash */
522     ascii_p16[32] = ':';
523     for (i = 0; i < 16; i++) {
524       sprintf(&ascii_p16[(i * 2)+33], "%02X", (uchar) new_nt_p16[i]);
525     }
526   }
527   /*
528    * Do an atomic write into the file at the position defined by
529    * seekpos.
530    */
531   pwfd = fileno(fp);
532   ret = lseek(pwfd, seekpos - 1, SEEK_SET);
533   if (ret != seekpos - 1) {
534     err = errno;
535     fprintf(stderr, "%s: seek fail on file %s.\n",
536             argv[0], pfile);
537     fclose(fp);
538     errno = err;
539     perror(argv[0]);
540     pw_file_unlock(lockfd);
541     exit(1);
542   }
543   /* Sanity check - ensure the character is a ':' */
544   if (read(pwfd, &c, 1) != 1) {
545     err = errno;
546     fprintf(stderr, "%s: read fail on file %s.\n",
547             argv[0], pfile);
548     fclose(fp);
549     errno = err;
550     perror(argv[0]);
551     pw_file_unlock(lockfd);
552     exit(1);
553   }
554   if (c != ':') {
555     fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
556             argv[0], pfile);
557     fclose(fp);
558     pw_file_unlock(lockfd);
559     exit(1);
560   }
561   writelen = (got_valid_nt_entry) ? 65 : 32;
562   if (write(pwfd, ascii_p16, writelen) != writelen) {
563     err = errno;
564     fprintf(stderr, "%s: write fail in file %s.\n",
565             argv[0], pfile);
566     fclose(fp);
567     errno = err;
568     perror(argv[0]);
569     pw_file_unlock(lockfd);
570     exit(err);
571   }
572   fclose(fp);
573   pw_file_unlock(lockfd);
574   printf("Password changed\n");
575   return 0;
576 }
577
578 #else
579
580 #include "includes.h"
581
582 int 
583 main(int argc, char **argv)
584 {
585   printf("smb password encryption not selected in Makefile\n");
586   return 0;
587 }
588 #endif