This is a security audit change of the main source.
[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 extern pstring myhostname;
23 extern pstring global_myname;
24 extern fstring global_myworkgroup;
25
26 static char *prog_name;
27
28 /*********************************************************
29  Print command usage on stderr and die.
30 **********************************************************/
31
32 static void usage(char *name, BOOL is_root)
33 {
34         if(is_root)
35                 fprintf(stderr, "Usage is : %s [-D DEBUGLEVEL] [-a] [-d] [-m] [-n] [username] [password]\n\
36 %s: [-R <name resolve order>] [-D DEBUGLEVEL] [-j DOMAINNAME] [-r machine] [username] [password]\n%s: [-h]\n", name, name, name);
37         else
38                 fprintf(stderr, "Usage is : %s [-h] [-D DEBUGLEVEL] [-r machine] [password]\n", name);
39         exit(1);
40 }
41
42 /*********************************************************
43 Join a domain.
44 **********************************************************/
45
46 static int setup_account( char *domain, char *remote_machine, 
47                           unsigned char orig_trust_passwd_hash[16],
48                           unsigned char new_trust_passwd_hash[16])
49 {
50   struct in_addr dest_ip;
51   struct cli_state cli;
52
53   memset(&cli, '\0', sizeof(struct cli_state));
54   if(cli_initialise(&cli) == False) {
55     fprintf(stderr, "%s: unable to initialize client connection.\n", prog_name);
56     return 1;
57   }
58
59   if(!resolve_name( remote_machine, &dest_ip)) {
60     fprintf(stderr, "%s: Can't resolve address for %s\n", prog_name, remote_machine);
61     return 1;
62   }
63
64   if (ismyip(dest_ip)) {
65     fprintf(stderr,"%s: Machine %s is one of our addresses. Cannot add to ourselves.\n", prog_name,
66            remote_machine);
67     return 1;
68   }
69
70   if (!cli_connect(&cli, remote_machine, &dest_ip)) {
71     fprintf(stderr, "%s: unable to connect to SMB server on \
72 machine %s. Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli) );
73     return 1;
74   }
75     
76   if (!cli_session_request(&cli, remote_machine, 0x20, global_myname)) {
77     fprintf(stderr, "%s: machine %s rejected the session setup. \
78 Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli) );
79     cli_shutdown(&cli);
80     return 1;
81   }
82     
83   cli.protocol = PROTOCOL_NT1;
84     
85   if (!cli_negprot(&cli)) {
86     fprintf(stderr, "%s: machine %s rejected the negotiate protocol. \
87 Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli) );
88     cli_shutdown(&cli);
89     return 1;
90   }
91   if (cli.protocol != PROTOCOL_NT1) {
92     fprintf(stderr, "%s: machine %s didn't negotiate NT protocol.\n", prog_name, remote_machine);
93     cli_shutdown(&cli);
94     return 1;
95   }
96     
97   /*
98    * Do an anonymous session setup.
99    */
100     
101   if (!cli_session_setup(&cli, "", "", 0, "", 0, "")) {
102     fprintf(stderr, "%s: machine %s rejected the session setup. \
103 Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli) );
104     cli_shutdown(&cli);
105     return 1;
106   }
107     
108   if (!(cli.sec_mode & 1)) {
109     fprintf(stderr, "%s: machine %s isn't in user level security mode\n", prog_name, remote_machine);
110     cli_shutdown(&cli);
111     return 1;
112   }
113     
114   if (!cli_send_tconX(&cli, "IPC$", "IPC", "", 1)) {
115     fprintf(stderr, "%s: machine %s rejected the tconX on the IPC$ share. \
116 Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli) );
117     cli_shutdown(&cli);
118     return 1;
119   }
120
121   /*
122    * Ok - we have an anonymous connection to the IPC$ share.
123    * Now start the NT Domain stuff :-).
124    */
125     
126   if(cli_nt_session_open(&cli, PIPE_NETLOGON, False) == False) {
127     fprintf(stderr, "%s: unable to open the domain client session to \
128 machine %s. Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli));
129     cli_nt_session_close(&cli);
130     cli_ulogoff(&cli);
131     cli_shutdown(&cli);
132     return 1;
133   } 
134   
135   if(cli_nt_setup_creds(&cli, orig_trust_passwd_hash) == False) {
136     fprintf(stderr, "%s: unable to setup the PDC credentials to machine \
137 %s. Error was : %s.\n", prog_name, remote_machine, cli_errstr(&cli));
138     cli_nt_session_close(&cli);
139     cli_ulogoff(&cli);
140     cli_shutdown(&cli);
141     return 1;
142   } 
143
144   if( cli_nt_srv_pwset( &cli,new_trust_passwd_hash ) == False) {
145     fprintf(stderr, "%s: unable to change password for machine %s in domain \
146 %s to Domain controller %s. Error was %s.\n", prog_name, global_myname, domain, remote_machine, 
147                             cli_errstr(&cli));
148     cli_close(&cli, cli.nt_pipe_fnum);
149     cli_ulogoff(&cli);
150     cli_shutdown(&cli);
151     return 1; 
152   }
153
154   cli_nt_session_close(&cli);
155   cli_ulogoff(&cli);
156   cli_shutdown(&cli);
157
158   return 0;
159 }
160
161 /*********************************************************
162 Join a domain.
163 **********************************************************/
164
165 static int join_domain( char *domain, char *remote)
166 {
167   fstring remote_machine;
168   char *p;
169   fstring trust_passwd;
170   unsigned char trust_passwd_hash[16];
171   unsigned char new_trust_passwd_hash[16];
172   int ret = 1;
173
174   fstrcpy(remote_machine, remote ? remote : "");
175   fstrcpy(trust_passwd, global_myname);
176   strlower(trust_passwd);
177   E_md4hash( (uchar *)trust_passwd, trust_passwd_hash);
178
179   generate_random_buffer( new_trust_passwd_hash, 16, True);
180
181   /* Ensure that we are not trying to join a
182      domain if we are locally set up as a domain
183      controller. */
184
185   if(lp_domain_controller() && strequal(lp_workgroup(), domain)) {
186     fprintf(stderr, "%s: Cannot join domain %s as we already configured as domain controller \
187 for that domain.\n", prog_name, domain);
188     return 1;
189   }
190
191   /*
192    * Write the new machine password.
193    */
194
195   /*
196    * Get the machine account password.
197    */
198   if(!trust_password_lock( domain, global_myname, True)) {
199     fprintf(stderr, "%s: unable to open the machine account password file for \
200 machine %s in domain %s.\n", prog_name, global_myname, domain); 
201     return 1;
202   }
203
204   if(!set_trust_account_password( new_trust_passwd_hash)) {              
205     fprintf(stderr, "%s: unable to read the machine account password for \
206 machine %s in domain %s.\n", prog_name, global_myname, domain);
207     trust_password_unlock();
208     return 1;
209   }
210
211   trust_password_unlock();
212
213   /*
214    * If we are given a remote machine assume this is the PDC.
215    */
216
217   if(remote != NULL) {
218     strupper(remote_machine);
219     ret = setup_account( domain, remote_machine, trust_passwd_hash, new_trust_passwd_hash);
220     if(ret == 0)
221       printf("%s: Joined domain %s.\n", prog_name, domain);
222   } else {
223     /*
224      * Treat each name in the 'password server =' line as a potential
225      * PDC/BDC. Contact each in turn and try and authenticate and
226      * change the machine account password.
227      */
228
229     p = lp_passwordserver();
230
231     if(!*p)
232       fprintf(stderr, "%s: No password server list given in smb.conf - \
233 unable to join domain.\n", prog_name);
234
235     while(p && next_token( &p, remote_machine, LIST_SEP)) {
236
237       strupper(remote_machine);
238       if(setup_account( domain, remote_machine, trust_passwd_hash, new_trust_passwd_hash) == 0) {
239         printf("%s: Joined domain %s.\n", prog_name, domain);
240         return 0;
241       }
242     }
243   }
244
245   if(ret) {
246     trust_password_delete( domain, global_myname);
247     fprintf(stderr,"%s: Unable to join domain %s.\n", prog_name, domain);
248   }
249
250   return ret;
251 }
252
253 /*********************************************************
254  Start here.
255 **********************************************************/
256
257 int main(int argc, char **argv)
258 {
259   extern char *optarg;
260   extern int optind;
261   extern int DEBUGLEVEL;
262   int             real_uid;
263   struct passwd  *pwd;
264   fstring         old_passwd;
265   fstring         new_passwd;
266   uchar           new_p16[16];
267   uchar           new_nt_p16[16];
268   char           *p;
269   struct smb_passwd *smb_pwent;
270   FILE           *fp;
271   int             ch;
272   int             err;
273   BOOL is_root = False;
274   pstring  user_name;
275   char *remote_machine = NULL;
276   BOOL add_user = False;
277   BOOL got_new_pass = False;
278   BOOL trust_account = False;
279   BOOL disable_user = False;
280   BOOL set_no_password = False;
281   BOOL joining_domain = False;
282   char *new_domain = NULL;
283   pstring servicesf = CONFIGFILE;
284   void           *vp;
285
286   new_passwd[0] = '\0';
287   user_name[0] = '\0';
288
289   memset(old_passwd, '\0', sizeof(old_passwd));
290   memset(new_passwd, '\0', sizeof(new_passwd));
291
292   prog_name = argv[0];
293
294   TimeInit();
295
296   setup_logging(prog_name,True);
297   
298   charset_initialise();
299
300   if (!lp_load(servicesf,True,False,False)) {
301     fprintf(stderr, "%s: Can't load %s - run testparm to debug it\n", prog_name, servicesf);
302   }
303
304   if(!get_myname(myhostname,NULL)) {
305     fprintf(stderr, "%s: unable to get my hostname.\n", prog_name );
306     exit(1);
307   }
308
309   /*
310    * Set the machine NETBIOS name if not already
311    * set from the config file. 
312    */ 
313     
314   if (!*global_myname)
315   {   
316     fstrcpy( global_myname, myhostname );
317     p = strchr( global_myname, '.' );
318     if (p) 
319       *p = 0;
320   }           
321   strupper( global_myname );
322
323   codepage_initialise(lp_client_code_page());
324
325   /* Get the real uid */
326   real_uid = getuid();
327   
328   /* Check the effective uid */
329   if ((geteuid() == 0) && (real_uid != 0)) {
330     fprintf(stderr, "%s: Must *NOT* be setuid root.\n", prog_name);
331     exit(1);
332   }
333
334   is_root = (real_uid == 0);
335
336   while ((ch = getopt(argc, argv, "adhmnj:r:R:D:")) != EOF) {
337     switch(ch) {
338     case 'a':
339       if(is_root)
340         add_user = True;
341       else
342         usage(prog_name, is_root);
343       break;
344     case 'd':
345       if(is_root) {
346         disable_user = True;
347         got_new_pass = True;
348         fstrcpy(new_passwd, "XXXXXX");
349       } else
350         usage(prog_name, is_root);
351       break;
352     case 'D':
353       DEBUGLEVEL = atoi(optarg);
354       break;
355     case 'n':
356       if(is_root) {
357         set_no_password = True;
358         got_new_pass = True;
359         fstrcpy(new_passwd, "NO PASSWORD");
360       } else
361         usage(prog_name, is_root);
362     case 'r':
363       remote_machine = optarg;
364       break;
365     case 'R':
366       if(is_root) {
367         lp_set_name_resolve_order(optarg);
368         break;
369       } else
370         usage(prog_name, is_root);
371     case 'm':
372       if(is_root) {
373         trust_account = True;
374       } else
375         usage(prog_name, is_root);
376       break;
377     case 'j':
378       if(is_root) {
379         new_domain = optarg;
380         strupper(new_domain);
381         joining_domain = True;
382       } else
383         usage(prog_name, is_root);  
384       break;
385     case 'h':
386     default:
387       usage(prog_name, is_root);
388     }
389   }
390
391   argc -= optind;
392   argv += optind;
393
394   /*
395    * Ensure add_user and either remote machine or join domain are
396    * not both set.
397    */
398
399   if(add_user && ((remote_machine != NULL) || joining_domain))
400     usage(prog_name, True);
401
402   /*
403    * Deal with joining a domain.
404    */
405   if(joining_domain && argc != 0)
406     usage(prog_name, True);
407
408   if(joining_domain) {
409     return join_domain( new_domain, remote_machine);
410   }
411
412   if(is_root) {
413
414     /*
415      * Deal with root - can add a user, but only locally.
416      */
417
418     switch(argc) {
419       case 0:
420         break;
421       case 1:
422         /* If we are root we can change another's password. */
423         pstrcpy(user_name, argv[0]);
424         break;
425       case 2:
426         pstrcpy(user_name, argv[0]);
427         fstrcpy(new_passwd, argv[1]);
428         got_new_pass = True;
429         break;
430       default:
431         usage(prog_name, True);
432     }
433
434     if(*user_name) {
435
436       if(trust_account) {
437         int username_len = strlen(user_name);
438         if(username_len >= sizeof(pstring) - 1) {
439           fprintf(stderr, "%s: machine account name too long.\n", user_name);
440           exit(1);
441         }
442
443         if(user_name[username_len-1] != '$') {
444           user_name[username_len] = '$';
445           user_name[username_len+1] = '\0';
446         }
447       }
448
449     /*
450      * Setup the pwd struct to point to known
451      * values for a machine account (it doesn't
452      * exist in /etc/passwd).
453      */
454       if((pwd = getpwnam(user_name)) == NULL) {
455         fprintf(stderr, "%s: User \"%s\" was not found in system password file.\n", 
456                     prog_name, user_name);
457         exit(1);
458       }
459     } else {
460       if((pwd = getpwuid(real_uid)) != NULL)
461         pstrcpy( user_name, pwd->pw_name);
462     }
463
464   } else {
465
466     if(add_user) {
467       fprintf(stderr, "%s: Only root can set anothers password.\n", prog_name);
468       usage(prog_name, False);
469     }
470
471     if(argc > 1)
472       usage(prog_name, False);
473
474     if(argc == 1) {
475       fstrcpy(new_passwd, argv[0]);
476       got_new_pass = True;
477     }
478
479     if((pwd = getpwuid(real_uid)) != NULL)
480       pstrcpy( user_name, pwd->pw_name);
481
482     /*
483      * A non-root user is always setting a password
484      * via a remote machine (even if that machine is
485      * localhost).
486      */
487
488     if(remote_machine == NULL)
489       remote_machine = "127.0.0.1";
490   }    
491     
492   if (*user_name == '\0') {
493     fprintf(stderr, "%s: Unable to get a user name for password change.\n", prog_name);
494     exit(1);
495   }
496
497   /*
498    * If we are adding a machine account then pretend
499    * we already have the new password, we will be using
500    * the machinename as the password.
501    */
502
503   if(add_user && trust_account) {
504     got_new_pass = True;
505     strncpy(new_passwd, user_name, sizeof(fstring));
506     new_passwd[sizeof(fstring)-1] = '\0';
507     strlower(new_passwd);
508     if(new_passwd[strlen(new_passwd)-1] == '$')
509       new_passwd[strlen(new_passwd)-1] = '\0';
510   }
511
512   /* 
513    * If we are root we don't ask for the old password (unless it's on a
514    * remote machine.
515    */
516
517   if (remote_machine != NULL) {
518     p = getpass("Old SMB password:");
519     fstrcpy(old_passwd, p);
520   }
521
522   if (!got_new_pass) {
523     new_passwd[0] = '\0';
524
525     p = getpass("New SMB password:");
526
527     strncpy(new_passwd, p, sizeof(fstring));
528     new_passwd[sizeof(fstring)-1] = '\0';
529
530     p = getpass("Retype new SMB password:");
531
532     if (strncmp(p, new_passwd, sizeof(fstring)-1))
533     {
534       fprintf(stderr, "%s: Mismatch - password unchanged.\n", prog_name);
535       exit(1);
536     }
537   }
538   
539   if (new_passwd[0] == '\0') {
540     printf("Password not set\n");
541     exit(0);
542   }
543  
544   /* 
545    * Now do things differently depending on if we're changing the
546    * password on a remote machine. Remember - a normal user is
547    * always using this code, looping back to the local smbd.
548    */
549
550   if(remote_machine != NULL) {
551     struct cli_state cli;
552     struct in_addr ip;
553
554     if(!resolve_name( remote_machine, &ip)) {
555       fprintf(stderr, "%s: unable to find an IP address for machine %s.\n",
556               prog_name, remote_machine );
557       exit(1);
558     }
559  
560     memset(&cli, '\0', sizeof(struct cli_state));
561  
562     if (!cli_initialise(&cli) || !cli_connect(&cli, remote_machine, &ip)) {
563       fprintf(stderr, "%s: unable to connect to SMB server on machine %s. Error was : %s.\n",
564               prog_name, remote_machine, cli_errstr(&cli) );
565       exit(1);
566     }
567   
568     if (!cli_session_request(&cli, remote_machine, 0x20, global_myname)) {
569       fprintf(stderr, "%s: machine %s rejected the session setup. Error was : %s.\n",
570               prog_name, remote_machine, cli_errstr(&cli) );
571       cli_shutdown(&cli);
572       exit(1);
573     }
574   
575     cli.protocol = PROTOCOL_NT1;
576
577     if (!cli_negprot(&cli)) {
578       fprintf(stderr, "%s: machine %s rejected the negotiate protocol. Error was : %s.\n",        
579               prog_name, remote_machine, cli_errstr(&cli) );
580       cli_shutdown(&cli);
581       exit(1);
582     }
583   
584     if (!cli_session_setup(&cli, user_name, old_passwd, strlen(old_passwd),
585                            "", 0, "")) {
586       fprintf(stderr, "%s: machine %s rejected the session setup. Error was : %s.\n",        
587               prog_name, remote_machine, cli_errstr(&cli) );
588       cli_shutdown(&cli);
589       exit(1);
590     }               
591
592     if (!cli_send_tconX(&cli, "IPC$", "IPC", "", 1)) {
593       fprintf(stderr, "%s: machine %s rejected the tconX on the IPC$ share. Error was : %s.\n",
594               prog_name, remote_machine, cli_errstr(&cli) );
595       cli_shutdown(&cli);
596       exit(1);
597     }
598
599     if(!cli_oem_change_password(&cli, user_name, new_passwd, old_passwd)) {
600       fprintf(stderr, "%s: machine %s rejected the password change: Error was : %s.\n",
601               prog_name, remote_machine, cli_errstr(&cli) );
602       cli_shutdown(&cli);
603       exit(1);
604     }
605
606     cli_shutdown(&cli);
607     exit(0);
608   }
609
610   /*
611    * Check for a machine account.
612    */
613
614   if(trust_account && !pwd) {
615     fprintf(stderr, "%s: User %s does not exist in system password file \
616 (usually /etc/passwd). Cannot add machine account without a valid system user.\n",
617            prog_name, user_name);
618     exit(1);
619   }
620
621   /* Calculate the MD4 hash (NT compatible) of the new password. */
622   
623   memset(new_nt_p16, '\0', 16);
624   E_md4hash((uchar *) new_passwd, new_nt_p16);
625   
626   /* Mangle the password into Lanman format */
627   new_passwd[14] = '\0';
628   strupper(new_passwd);
629   
630   /*
631    * Calculate the SMB (lanman) hash functions of the new password.
632    */
633   
634   memset(new_p16, '\0', 16);
635   E_P16((uchar *) new_passwd, new_p16);
636   
637   /*
638    * Open the smbpaswd file.
639    */
640   vp = startsampwent(True);
641   if (!vp && errno == ENOENT) {
642           fp = fopen(lp_smb_passwd_file(), "w");
643           if (fp) {
644                   fprintf(fp, "# Samba SMB password file\n");
645                   fclose(fp);
646                   vp = startsampwent(True);
647           }
648   }
649   if (!vp) {
650           err = errno;
651           fprintf(stderr, "%s: Failed to open password file %s.\n",
652                   prog_name, lp_smb_passwd_file());
653           errno = err;
654           perror(prog_name);
655           exit(err);
656   }
657   
658   /* Get the smb passwd entry for this user */
659   smb_pwent = getsampwnam(user_name);
660   if (smb_pwent == NULL) {
661     if(add_user == False) {
662       fprintf(stderr, "%s: Failed to find entry for user %s.\n",
663               prog_name, pwd->pw_name);
664       endsampwent(vp);
665       exit(1);
666     }
667
668     /* Create a new smb passwd entry and set it to the given password. */
669     {
670       struct smb_passwd new_smb_pwent;
671
672       new_smb_pwent.smb_userid = pwd->pw_uid;
673       new_smb_pwent.smb_name = pwd->pw_name; 
674       new_smb_pwent.smb_passwd = NULL;
675       new_smb_pwent.smb_nt_passwd = NULL;
676       new_smb_pwent.acct_ctrl = (trust_account ? ACB_WSTRUST : ACB_NORMAL);
677
678       if(disable_user) {
679         new_smb_pwent.acct_ctrl |= ACB_DISABLED;
680       } else if (set_no_password) {
681         new_smb_pwent.acct_ctrl |= ACB_PWNOTREQ;
682       } else {
683         new_smb_pwent.smb_passwd = new_p16;
684         new_smb_pwent.smb_nt_passwd = new_nt_p16;
685       }
686
687       if(add_sampwd_entry(&new_smb_pwent) == False) {
688         fprintf(stderr, "%s: Failed to add entry for user %s.\n", 
689                 prog_name, pwd->pw_name);
690         endsampwent(vp);
691         exit(1);
692       }
693       
694       endsampwent(vp);
695       printf("%s: Added user %s.\n", prog_name, user_name);
696       exit(0);
697     }
698   } else {
699           /* the entry already existed */
700           add_user = False;
701   }
702
703   /*
704    * We are root - just write the new password
705    * and the valid last change time.
706    */
707
708   if(disable_user) {
709     /*
710      * This currently won't work as it means changing
711      * the length of the record. JRA.
712      */
713     smb_pwent->acct_ctrl |= ACB_DISABLED;
714     smb_pwent->smb_passwd = NULL;
715     smb_pwent->smb_nt_passwd = NULL;
716   } else if (set_no_password) {
717     /*
718      * This currently won't work as it means changing
719      * the length of the record. JRA.
720      */
721     smb_pwent->acct_ctrl |= ACB_PWNOTREQ;
722     smb_pwent->smb_passwd = NULL;
723     smb_pwent->smb_nt_passwd = NULL; 
724   } else {
725     smb_pwent->smb_passwd = new_p16;
726     smb_pwent->smb_nt_passwd = new_nt_p16;
727   }
728
729   if(mod_sampwd_entry(smb_pwent,True) == False) {
730     fprintf(stderr, "%s: Failed to modify entry for user %s.\n",
731             prog_name, pwd->pw_name);
732     endsampwent(vp);
733     exit(1);
734   }
735
736   endsampwent(vp);
737   if(disable_user)
738     printf("User %s disabled.\n", user_name);
739   else if (set_no_password)
740     printf("User %s - set to no password.\n", user_name);
741   else
742     printf("Password changed for user %s.\n", user_name);
743   return 0;
744 }