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