Added writing of '[XXX]' account control bits into gcos
[ira/wip.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 /******************************************************
41  Convert a hex password.
42 *******************************************************/
43
44 static int gethexpwd(char *p, char *pwd)
45 {
46         int i;
47         unsigned char   lonybble, hinybble;
48         char           *hexchars = "0123456789ABCDEF";
49         char           *p1, *p2;
50         for (i = 0; i < 32; i += 2) {
51                 hinybble = toupper(p[i]);
52                 lonybble = toupper(p[i + 1]);
53
54                 p1 = strchr(hexchars, hinybble);
55                 p2 = strchr(hexchars, lonybble);
56                 if (!p1 || !p2)
57                         return (False);
58
59                 hinybble = PTR_DIFF(p1, hexchars);
60                 lonybble = PTR_DIFF(p2, hexchars);
61
62                 pwd[i / 2] = (hinybble << 4) | lonybble;
63         }
64         return (True);
65 }
66
67 /******************************************************
68  Find a password entry by name.
69 *******************************************************/
70
71 static struct smb_passwd *
72 _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd, 
73                 BOOL *got_valid_nt_entry, long *pwd_seekpos)
74 {
75         /* Static buffers we will return. */
76         static struct smb_passwd pw_buf;
77         static pstring  user_name;
78         static unsigned char smbpwd[16];
79         static unsigned char smbntpwd[16];
80
81         char            linebuf[256];
82         unsigned char   c;
83         unsigned char  *p;
84         long            uidval;
85         long            linebuf_len;
86
87         pw_buf.acct_ctrl = ACB_NORMAL;
88
89         /*
90          * Scan the file, a line at a time and check if the name matches.
91          */
92         while (!feof(fp)) {
93                 linebuf[0] = '\0';
94                 *pwd_seekpos = ftell(fp);
95
96                 fgets(linebuf, 256, fp);
97                 if (ferror(fp))
98                         return NULL;
99
100                 /*
101                  * Check if the string is terminated with a newline - if not
102                  * then we must keep reading and discard until we get one.
103                  */
104                 linebuf_len = strlen(linebuf);
105                 if (linebuf[linebuf_len - 1] != '\n') {
106                         c = '\0';
107                         while (!ferror(fp) && !feof(fp)) {
108                                 c = fgetc(fp);
109                                 if (c == '\n')
110                                         break;
111                         }
112                 } else
113                         linebuf[linebuf_len - 1] = '\0';
114
115                 if ((linebuf[0] == 0) && feof(fp))
116                         break;
117                 /*
118                  * The line we have should be of the form :-
119                  * 
120                  * username:uid:[32hex bytes]:....other flags presently
121                  * ignored....
122                  * 
123                  * or,
124                  * 
125                  * username:uid:[32hex bytes]:[32hex bytes]:....ignored....
126                  * 
127                  * if Windows NT compatible passwords are also present.
128                  */
129
130                 if (linebuf[0] == '#' || linebuf[0] == '\0')
131                         continue;
132                 p = (unsigned char *) strchr(linebuf, ':');
133                 if (p == NULL)
134                         continue;
135                 /*
136                  * As 256 is shorter than a pstring we don't need to check
137                  * length here - if this ever changes....
138                  */
139                 strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
140                 user_name[PTR_DIFF(p, linebuf)] = '\0';
141                 if (!strequal(user_name, name))
142                         continue;
143
144                 /* User name matches - get uid and password */
145                 p++;            /* Go past ':' */
146                 if (!isdigit(*p))
147                         return (False);
148
149                 uidval = atoi((char *) p);
150                 while (*p && isdigit(*p))
151                         p++;
152
153                 if (*p != ':')
154                         return (False);
155
156                 /*
157                  * Now get the password value - this should be 32 hex digits
158                  * which are the ascii representations of a 16 byte string.
159                  * Get two at a time and put them into the password.
160                  */
161                 p++;
162                 *pwd_seekpos += PTR_DIFF(p, linebuf);   /* Save exact position
163                                                          * of passwd in file -
164                                                          * this is used by
165                                                          * smbpasswd.c */
166                 if (*p == '*' || *p == 'X') {
167                         /* Password deliberately invalid - end here. */
168                         *valid_old_pwd = False;
169                         *got_valid_nt_entry = False;
170                         pw_buf.smb_nt_passwd = NULL;    /* No NT password (yet)*/
171
172                         pw_buf.acct_ctrl |= ACB_DISABLED;
173
174                         /* Now check if the NT compatible password is
175                            available. */
176                         p += 33; /* Move to the first character of the line after 
177                                                 the lanman password. */
178                         if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
179                                 /* NT Entry was valid - even if 'X' or '*', can be overwritten */
180                                 *got_valid_nt_entry = True;
181                                 if (*p != '*' && *p != 'X') {
182                                   if (gethexpwd((char *)p,(char *)smbntpwd))
183                                     pw_buf.smb_nt_passwd = smbntpwd;
184                                 }
185                         }
186                         pw_buf.smb_name = user_name;
187                         pw_buf.smb_userid = uidval;
188                         pw_buf.smb_passwd = NULL;       /* No password */
189                         return (&pw_buf);
190                 }
191                 if (linebuf_len < (PTR_DIFF(p, linebuf) + 33))
192                         return (False);
193
194                 if (p[32] != ':')
195                         return (False);
196
197                 if (!strncasecmp((char *)p, "NO PASSWORD", 11)) {
198                   pw_buf.smb_passwd = NULL;     /* No password */
199                   pw_buf.acct_ctrl |= ACB_PWNOTREQ;
200                 } else {
201                   if(!gethexpwd((char *)p,(char *)smbpwd))
202                     return False;
203                   pw_buf.smb_passwd = smbpwd;
204                 }
205
206                 pw_buf.smb_name = user_name;
207                 pw_buf.smb_userid = uidval;
208                 pw_buf.smb_nt_passwd = NULL;
209                 *got_valid_nt_entry = False;
210                 *valid_old_pwd = True;
211
212                 /* Now check if the NT compatible password is
213                    available. */
214                 p += 33; /* Move to the first character of the line after 
215                                         the lanman password. */
216                 if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
217                         /* NT Entry was valid - even if 'X' or '*', can be overwritten */
218                         *got_valid_nt_entry = True;
219                         if (*p != '*' && *p != 'X') {
220                           if (gethexpwd((char *)p,(char *)smbntpwd))
221                             pw_buf.smb_nt_passwd = smbntpwd;
222                         }
223
224                         p += 33; /* Move to the first character of the line after
225                                     the NT password. */
226                 }
227
228                 /*
229                  * Check if the account type bits have been encoded after the
230                  * NT password (in the form [NDHTUWSLXI]).
231                  */
232             
233                 if (*p == '[') {
234                   BOOL finished = False;
235             
236                   pw_buf.acct_ctrl = 0;
237             
238                   for(p++;*p && !finished; p++) {
239                     switch (*p) {
240 #if 0
241                       /* 
242                        * Hmmm. Don't allow these to be set/read independently
243                        * of the actual password fields. We don't want a mismatch.
244                        * JRA.
245                        */
246                       case 'N':
247                         /* 'N'o password. */
248                         pw_buf.acct_ctrl |= ACB_PWNOTREQ;
249                         break;
250                       case 'D':
251                         /* 'D'isabled. */
252                         pw_buf.acct_ctrl |= ACB_DISABLED;
253                         break;
254 #endif
255                       case 'H':
256                         /* 'H'omedir required. */
257                         pw_buf.acct_ctrl |= ACB_HOMDIRREQ;
258                         break;
259                       case 'T':
260                         /* 'T'emp account. */
261                         pw_buf.acct_ctrl |= ACB_TEMPDUP;
262                         break;
263                       case 'U':
264                         /* 'U'ser account (normal). */
265                         pw_buf.acct_ctrl |= ACB_NORMAL;
266                         break;
267                       case 'M':
268                         /* 'M'NS logon user account. What is this ? */
269                         pw_buf.acct_ctrl |= ACB_MNS;
270                         break;
271                       case 'W':
272                         /* 'W'orkstation account. */
273                         pw_buf.acct_ctrl |= ACB_WSTRUST;
274                         break;
275                       case 'S':
276                         /* 'S'erver account. */
277                         pw_buf.acct_ctrl |= ACB_SVRTRUST;
278                         break;
279                       case 'L':
280                         /* 'L'ocked account. */
281                         pw_buf.acct_ctrl |= ACB_AUTOLOCK;
282                         break;
283                       case 'X':
284                         /* No 'X'piry. */
285                         pw_buf.acct_ctrl |= ACB_PWNOEXP;
286                         break;
287                       case 'I':
288                         /* 'I'nterdomain trust account. */
289                         pw_buf.acct_ctrl |= ACB_DOMTRUST;
290                         break;
291             
292                       case ':':
293                       case '\n':
294                       case '\0': 
295                       case ']':
296                       default:
297                         finished = True;
298                     }
299                   }
300             
301                   /* Must have some account type set. */
302                   if(pw_buf.acct_ctrl == 0)
303                     pw_buf.acct_ctrl = ACB_NORMAL;
304             
305                 } else {
306                   /* 'Old' style file. Fake up based on user name. */
307                   /*
308                    * Currently machine accounts are kept in the same
309                    * password file as 'normal accounts'. If this changes
310                    * we will have to fix this code. JRA.
311                    */
312                   if(pw_buf.smb_name[strlen(pw_buf.smb_name) - 1] == '$') {
313                     pw_buf.acct_ctrl &= ~ACB_NORMAL;
314                     pw_buf.acct_ctrl |= ACB_WSTRUST;
315                   }
316                 }
317                 return &pw_buf;
318         }
319         return NULL;
320 }
321
322 /**********************************************************
323  Encode the account control bits into a string.
324 **********************************************************/
325
326 char *encode_acct_ctrl(uint16 acct_ctrl)
327 {
328   static fstring acct_str;
329   char *p = acct_str;
330
331   *p++ = '[';
332
333   if(acct_ctrl & ACB_HOMDIRREQ)
334     *p++ = 'H';
335   if(acct_ctrl & ACB_TEMPDUP)
336     *p++ = 'T';
337   if(acct_ctrl & ACB_NORMAL)
338     *p++ = 'U';
339   if(acct_ctrl & ACB_MNS)
340     *p++ = 'M';
341   if(acct_ctrl & ACB_WSTRUST)
342     *p++ = 'W';
343   if(acct_ctrl & ACB_SVRTRUST)
344     *p++ = 'S';
345   if(acct_ctrl & ACB_AUTOLOCK)
346     *p++ = 'L';
347   if(acct_ctrl & ACB_PWNOEXP)
348     *p++ = 'X';
349   if(acct_ctrl & ACB_DOMTRUST)
350     *p++ = 'I';
351
352   *p++ = ']';
353   *p = '\0';
354   return acct_str;
355 }
356
357 /**********************************************************
358  Allocate an unused uid in the smbpasswd file to a new
359  machine account.
360 ***********************************************************/
361
362 int get_machine_uid(void)
363 {
364   return 65534;
365 }
366
367 /*********************************************************
368  Print command usage on stderr and die.
369 **********************************************************/
370
371 static void usage(char *name, BOOL is_root)
372 {
373         if(is_root)
374                 fprintf(stderr, "Usage is : %s [-a] [-d] [-m] [-n] [username] [password]\n\
375 %s: [-r machine] [username] [password]\n%s: [-h]\n", name, name, name);
376         else
377                 fprintf(stderr, "Usage is : %s [-h] [-r machine] [password]\n", name);
378         exit(1);
379 }
380
381 /*********************************************************
382  Start here.
383 **********************************************************/
384
385 int main(int argc, char **argv)
386 {
387   extern char *optarg;
388   extern int optind;
389   char *prog_name;
390   int             real_uid;
391   struct passwd  *pwd;
392   struct passwd   machine_account_pwd;
393   fstring         old_passwd;
394   fstring         new_passwd;
395   uchar           new_p16[16];
396   uchar           new_nt_p16[16];
397   char           *p;
398   struct smb_passwd *smb_pwent;
399   FILE           *fp;
400   BOOL            valid_old_pwd = False;
401   BOOL           got_valid_nt_entry = False;
402   long            seekpos;
403   int             pwfd;
404   char            ascii_p16[66];
405   char            c;
406   int             ch;
407   int             ret, i, err, writelen;
408   int             lockfd = -1;
409   char           *pfile = SMB_PASSWD_FILE;
410   char            readbuf[16 * 1024];
411   BOOL is_root = False;
412   pstring  user_name;
413   pstring  machine_dir_name;
414   char *remote_machine = NULL;
415   BOOL           add_user = False;
416   BOOL           got_new_pass = False;
417   BOOL           machine_account = False;
418   BOOL           disable_user = False;
419   BOOL           set_no_password = False;
420   pstring servicesf = CONFIGFILE;
421
422   new_passwd[0] = '\0';
423   user_name[0] = '\0';
424
425   memset(old_passwd, '\0', sizeof(old_passwd));
426   memset(new_passwd, '\0', sizeof(new_passwd));
427
428   prog_name = argv[0];
429
430   TimeInit();
431
432   setup_logging(prog_name,True);
433   
434   charset_initialise();
435
436   if (!lp_load(servicesf,True,False,False)) {
437     fprintf(stderr, "%s: Can't load %s - run testparm to debug it\n", prog_name, servicesf);
438   }
439     
440   codepage_initialise(lp_client_code_page());
441
442   /* Get the real uid */
443   real_uid = getuid();
444   
445   /* Check the effective uid */
446   if ((geteuid() == 0) && (real_uid != 0)) {
447     fprintf(stderr, "%s: Must *NOT* be setuid root.\n", prog_name);
448     exit(1);
449   }
450
451   is_root = (real_uid == 0);
452
453   while ((ch = getopt(argc, argv, "adhmnr:")) != EOF) {
454     switch(ch) {
455     case 'a':
456       if(is_root)
457         add_user = True;
458       else
459         usage(prog_name, is_root);
460       break;
461     case 'd':
462       if(is_root) {
463         disable_user = True;
464         got_new_pass = True;
465         strcpy(new_passwd, "XXXXXX");
466       } else
467         usage(prog_name, is_root);
468       break;
469     case 'n':
470       if(is_root) {
471         set_no_password = True;
472         got_new_pass = True;
473         strcpy(new_passwd, "NO PASSWORD");
474       } else
475         usage(prog_name, is_root);
476     case 'r':
477       remote_machine = optarg;
478       break;
479     case 'm':
480       if(is_root)
481         machine_account = True;
482       else
483         usage(prog_name, is_root);
484       break;
485     case 'h':
486     default:
487       usage(prog_name, is_root);
488     }
489   }
490
491   argc -= optind;
492   argv += optind;
493
494   /*
495    * Ensure add_user and remote machine are
496    * not both set.
497    */
498   if(add_user && (remote_machine != NULL))
499     usage(prog_name, True);
500
501   if( is_root ) {
502
503     /*
504      * Deal with root - can add a user, but only locally.
505      */
506
507     switch(argc) {
508       case 0:
509         break;
510       case 1:
511         /* If we are root we can change another's password. */
512         pstrcpy(user_name, argv[0]);
513         break;
514       case 2:
515         pstrcpy(user_name, argv[0]);
516         fstrcpy(new_passwd, argv[1]);
517         got_new_pass = True;
518         break;
519       default:
520         usage(prog_name, True);
521     }
522
523     if(*user_name)
524       pwd = getpwnam(user_name);
525     else {
526       if((pwd = getpwuid(real_uid)) != NULL)
527         pstrcpy( user_name, pwd->pw_name);
528     }
529
530   } else {
531
532     if(add_user) {
533       fprintf(stderr, "%s: Only root can set anothers password.\n", prog_name);
534       usage(prog_name, False);
535     }
536
537     if(argc > 1)
538       usage(prog_name, False);
539
540     if(argc == 1) {
541       fstrcpy(new_passwd, argv[0]);
542       got_new_pass = True;
543     }
544
545     if((pwd = getpwuid(real_uid)) != NULL)
546       pstrcpy( user_name, pwd->pw_name);
547
548     /*
549      * A non-root user is always setting a password
550      * via a remote machine (even if that machine is
551      * localhost).
552      */
553
554     if(remote_machine == NULL)
555       remote_machine = "127.0.0.1";
556   }    
557     
558   if (*user_name == '\0') {
559     fprintf(stderr, "%s: Unable to get a user name for password change.\n", prog_name);
560     exit(1);
561   }
562
563   /*
564    * If we are adding a machine account then pretend
565    * we already have the new password, we will be using
566    * the machinename as the password.
567    */
568
569   if(add_user && machine_account) {
570     got_new_pass = True;
571     strncpy(new_passwd, user_name, sizeof(fstring));
572     new_passwd[sizeof(fstring)-1] = '\0';
573     strlower(new_passwd);
574   }
575
576   /* 
577    * If we are root we don't ask for the old password (unless it's on a
578    * remote machine.
579    */
580
581   if (remote_machine != NULL) {
582     p = getpass("Old SMB password:");
583     fstrcpy(old_passwd, p);
584   }
585
586   if (!got_new_pass) {
587     new_passwd[0] = '\0';
588
589     p = getpass("New SMB password:");
590
591     strncpy(new_passwd, p, sizeof(fstring));
592     new_passwd[sizeof(fstring)-1] = '\0';
593
594     p = getpass("Retype new SMB password:");
595
596     if (strncmp(p, new_passwd, sizeof(fstring)-1))
597     {
598       fprintf(stderr, "%s: Mismatch - password unchanged.\n", prog_name);
599       exit(1);
600     }
601   }
602   
603   if (new_passwd[0] == '\0') {
604     printf("Password not set\n");
605     exit(0);
606   }
607  
608   /* 
609    * Now do things differently depending on if we're changing the
610    * password on a remote machine. Remember - a normal user is
611    * always using this code, looping back to the local smbd.
612    */
613
614   if(remote_machine != NULL) {
615     struct cli_state cli;
616     struct in_addr ip;
617     fstring myname;
618
619     if(get_myname(myname,NULL) == False) {
620       fprintf(stderr, "%s: unable to get my hostname.\n", prog_name );
621       exit(1);
622     }
623
624     if(!resolve_name( remote_machine, &ip)) {
625       fprintf(stderr, "%s: unable to find an IP address for machine %s.\n",
626               prog_name, remote_machine );
627       exit(1);
628     }
629   
630     if (!cli_initialise(&cli) || !cli_connect(&cli, remote_machine, &ip)) {
631       fprintf(stderr, "%s: unable to connect to SMB server on machine %s.\n",
632               prog_name, remote_machine );
633       exit(1);
634     }
635   
636     if (!cli_session_request(&cli, remote_machine, 0x20, myname)) {
637       fprintf(stderr, "%s: machine %s rejected the session setup.\n",
638               prog_name, remote_machine );
639       cli_shutdown(&cli);
640       exit(1);
641     }
642   
643     cli.protocol = PROTOCOL_NT1;
644
645     if (!cli_negprot(&cli)) {
646       fprintf(stderr, "%s: machine %s rejected the negotiate protocol.\n",        
647               prog_name, remote_machine );
648       cli_shutdown(&cli);
649       exit(1);
650     }
651   
652     if (!cli_session_setup(&cli, user_name, old_passwd, strlen(old_passwd),
653                            "", 0, "")) {
654       fprintf(stderr, "%s: machine %s rejected the session setup.\n",        
655               prog_name, remote_machine );
656       cli_shutdown(&cli);
657       exit(1);
658     }               
659
660     if (!cli_send_tconX(&cli, "IPC$", "IPC", "", 1)) {
661       fprintf(stderr, "%s: machine %s rejected the tconX on the IPC$ share.\n",
662               prog_name, remote_machine );
663       cli_shutdown(&cli);
664       exit(1);
665     }
666
667     if(!cli_oem_change_password(&cli, user_name, new_passwd, old_passwd)) {
668       fstring error_message;
669
670       sprintf(error_message, " with code %d", cli.error);
671       
672       for(i = 0; pw_change_errmap[i].message != NULL; i++) {
673         if (pw_change_errmap[i].err == cli.error) {
674           fstrcpy( error_message, pw_change_errmap[i].message);
675           break;
676         }
677       }
678       fprintf(stderr, "%s: machine %s rejected the password change: %s.\n",
679               prog_name, remote_machine, error_message );
680       cli_shutdown(&cli);
681       exit(1);
682     }
683
684     cli_shutdown(&cli);
685     exit(0);
686   }
687
688   /*
689    * Check for a machine account flag - make sure the username ends in
690    * a '$' etc....
691    */
692
693   if(machine_account) {
694     int username_len = strlen(user_name);
695     if(username_len >= sizeof(pstring) - 1) {
696       fprintf(stderr, "%s: machine account name too long.\n", user_name);
697       exit(1);
698     }
699
700     if(user_name[username_len] != '$') {
701       user_name[username_len] = '$';
702       user_name[username_len+1] = '\0';
703     }
704
705     /*
706      * Setup the pwd struct to point to known
707      * values for a machine account (it doesn't
708      * exist in /etc/passwd).
709      */
710
711     pwd = &machine_account_pwd;
712     pwd->pw_name = user_name;
713     sprintf(machine_dir_name, "Machine account for %s", user_name);
714     pwd->pw_gecos = "";
715     pwd->pw_dir = machine_dir_name;
716     pwd->pw_shell = "";
717     pwd->pw_uid = get_machine_uid();
718       
719   }
720
721   /* Calculate the MD4 hash (NT compatible) of the new password. */
722   
723   memset(new_nt_p16, '\0', 16);
724   E_md4hash((uchar *) new_passwd, new_nt_p16);
725   
726   /* Mangle the password into Lanman format */
727   new_passwd[14] = '\0';
728   strupper(new_passwd);
729   
730   /*
731    * Calculate the SMB (lanman) hash functions of the new password.
732    */
733   
734   memset(new_p16, '\0', 16);
735   E_P16((uchar *) new_passwd, new_p16);
736   
737   /*
738    * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
739    * filename
740    */
741   fp = fopen(pfile, "r+");
742   if (!fp && errno == ENOENT) {
743           fp = fopen(pfile, "w");
744           if (fp) {
745                   fprintf(fp, "# Samba SMB password file\n");
746                   fclose(fp);
747                   fp = fopen(pfile, "r+");
748           }
749   }
750   if (!fp) {
751           err = errno;
752           fprintf(stderr, "%s: Failed to open password file %s.\n",
753                   prog_name, pfile);
754           errno = err;
755           perror(prog_name);
756           exit(err);
757   }
758   
759   /* Set read buffer to 16k for effiecient reads */
760   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
761   
762   /* make sure it is only rw by the owner */
763   chmod(pfile, 0600);
764
765   /* Lock the smbpasswd file for write. */
766   if ((lockfd = pw_file_lock(fileno(fp), F_WRLCK, 5)) < 0) {
767     err = errno;
768     fprintf(stderr, "%s: Failed to lock password file %s.\n",
769             prog_name, pfile);
770     fclose(fp);
771     errno = err;
772     perror(prog_name);
773     exit(err);
774   }
775
776   /* Get the smb passwd entry for this user */
777   smb_pwent = _my_get_smbpwnam(fp, user_name, &valid_old_pwd, 
778                                &got_valid_nt_entry, &seekpos);
779   if (smb_pwent == NULL) {
780     if(add_user == False) {
781       fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
782               prog_name, pwd->pw_name, pfile);
783       fclose(fp);
784       pw_file_unlock(lockfd);
785       exit(1);
786     }
787
788     /* Create a new smb passwd entry and set it to the given password. */
789     {
790       int fd;
791       int new_entry_length;
792       char *new_entry;
793       long offpos;
794       uint16 acct_ctrl = (machine_account ? ACB_SVRTRUST : ACB_NORMAL);
795
796       /* The add user write needs to be atomic - so get the fd from 
797          the fp and do a raw write() call.
798        */
799       fd = fileno(fp);
800
801       if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
802         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
803 Error was %s\n", prog_name, user_name, pfile, strerror(errno));
804         fclose(fp);
805         pw_file_unlock(lockfd);
806         exit(1);
807       }
808
809       new_entry_length = strlen(user_name) + 1 + 15 + 1 + 
810                          32 + 1 + 32 + 1 + sizeof(fstring) + 
811                          1 + strlen(pwd->pw_dir) + 1 + 
812                          strlen(pwd->pw_shell) + 1;
813       if((new_entry = (char *)malloc( new_entry_length )) == 0) {
814         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
815 Error was %s\n", prog_name, pwd->pw_name, pfile, strerror(errno));
816         pw_file_unlock(lockfd);
817         fclose(fp);
818         exit(1);
819       }
820
821       sprintf(new_entry, "%s:%u:", pwd->pw_name, (unsigned)pwd->pw_uid);
822       p = &new_entry[strlen(new_entry)];
823       if(disable_user) {
824         memcpy(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32);
825         p += 32;
826         *p++ = ':';
827         memcpy(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32);
828       } else if (set_no_password) {
829         memcpy(p, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX", 32);
830         p += 32;
831         *p++ = ':';
832         memcpy(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32);
833       } else {
834         for( i = 0; i < 16; i++)
835           sprintf(&p[i*2], "%02X", new_p16[i]);
836         p += 32;
837         *p++ = ':';
838         for( i = 0; i < 16; i++)
839           sprintf(&p[i*2], "%02X", new_nt_p16[i]);
840       }
841       p += 32;
842       *p++ = ':';
843       sprintf(p, "%s:%s:%s\n", encode_acct_ctrl(acct_ctrl),
844               pwd->pw_dir, pwd->pw_shell);
845       if(write(fd, new_entry, strlen(new_entry)) != strlen(new_entry)) {
846         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
847 Error was %s\n", prog_name, pwd->pw_name, pfile, strerror(errno));
848         /* Remove the entry we just wrote. */
849         if(ftruncate(fd, offpos) == -1) {
850           fprintf(stderr, "%s: ERROR failed to ftruncate file %s. \
851 Error was %s. Password file may be corrupt ! Please examine by hand !\n", 
852                    prog_name, pwd->pw_name, strerror(errno));
853         }
854         pw_file_unlock(lockfd);
855         fclose(fp);
856         exit(1);
857       }
858       
859       pw_file_unlock(lockfd);  
860       fclose(fp);  
861       exit(0);
862     }
863   } else {
864           /* the entry already existed */
865           add_user = False;
866   }
867
868   /*
869    * We are root - just write the new password.
870    */
871
872   /* Create the 32 byte representation of the new p16 */
873   if(disable_user) {
874     memcpy(ascii_p16, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32);
875   } else if (set_no_password) {
876     memcpy(ascii_p16, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX", 32);
877   } else {
878     for (i = 0; i < 16; i++) {
879       sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
880     }
881   }
882   if(got_valid_nt_entry) {
883     /* Add on the NT md4 hash */
884     ascii_p16[32] = ':';
885     if(disable_user) {
886       memcpy(&ascii_p16[33], "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32);
887     } else if (set_no_password) {
888       memcpy(&ascii_p16[33], "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32);
889     } else {
890       for (i = 0; i < 16; i++) {
891         sprintf(&ascii_p16[(i * 2)+33], "%02X", (uchar) new_nt_p16[i]);
892       }
893     }
894   }
895   /*
896    * Do an atomic write into the file at the position defined by
897    * seekpos.
898    */
899   pwfd = fileno(fp);
900   ret = lseek(pwfd, seekpos - 1, SEEK_SET);
901   if (ret != seekpos - 1) {
902     err = errno;
903     fprintf(stderr, "%s: seek fail on file %s.\n",
904             prog_name, pfile);
905     errno = err;
906     perror(prog_name);
907     pw_file_unlock(lockfd);
908     fclose(fp);
909     exit(1);
910   }
911   /* Sanity check - ensure the character is a ':' */
912   if (read(pwfd, &c, 1) != 1) {
913     err = errno;
914     fprintf(stderr, "%s: read fail on file %s.\n",
915             prog_name, pfile);
916     errno = err;
917     perror(prog_name);
918     pw_file_unlock(lockfd);
919     fclose(fp);
920     exit(1);
921   }
922   if (c != ':') {
923     fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
924             prog_name, pfile);
925     pw_file_unlock(lockfd);
926     fclose(fp);
927     exit(1);
928   }
929   writelen = (got_valid_nt_entry) ? 65 : 32;
930   if (write(pwfd, ascii_p16, writelen) != writelen) {
931     err = errno;
932     fprintf(stderr, "%s: write fail in file %s.\n",
933             prog_name, pfile);
934     errno = err;
935     perror(prog_name);
936     pw_file_unlock(lockfd);
937     fclose(fp);
938     exit(err);
939   }
940   pw_file_unlock(lockfd);
941   fclose(fp);
942   if(disable_user)
943     printf("User %s disabled.\n", user_name);
944   else if (set_no_password)
945     printf("User %s - set to no password.\n", user_name);
946   else
947     printf("Password changed for user %s.\n", user_name);
948   return 0;
949 }