renamed last_change_time to pass_must_change_time. removed "if (bool==True)"
[samba.git] / source3 / passdb / smbpass.c
1 /*
2  * Unix SMB/Netbios implementation. Version 1.9. SMB parameters and setup
3  * Copyright (C) Andrew Tridgell 1992-1998 Modified by Jeremy Allison 1995.
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 int DEBUGLEVEL;
23
24 static int gotalarm;
25 static char s_readbuf[16 * 1024];
26
27 /***************************************************************
28  Signal function to tell us we timed out.
29 ****************************************************************/
30
31 static void gotalarm_sig(void)
32 {
33   gotalarm = 1;
34 }
35
36 /***************************************************************
37  Lock or unlock a fd for a known lock type. Abandon after waitsecs 
38  seconds.
39 ****************************************************************/
40
41 static BOOL do_pw_lock(int fd, int waitsecs, int type)
42 {
43   struct flock    lock;
44   int             ret;
45
46   gotalarm = 0;
47   signal(SIGALRM, SIGNAL_CAST gotalarm_sig);
48
49   lock.l_type = type;
50   lock.l_whence = SEEK_SET;
51   lock.l_start = 0;
52   lock.l_len = 1;
53   lock.l_pid = 0;
54
55   alarm(5);
56   ret = fcntl(fd, F_SETLKW, &lock);
57   alarm(0);
58   signal(SIGALRM, SIGNAL_CAST SIG_DFL);
59
60   if (gotalarm) {
61     DEBUG(0, ("do_pw_lock: failed to %s SMB passwd file.\n",
62                 type == F_UNLCK ? "unlock" : "lock"));
63     return False;
64   }
65
66   return (ret == 0);
67 }
68
69 static int pw_file_lock_depth;
70
71 /***************************************************************
72  Lock an fd. Abandon after waitsecs seconds.
73 ****************************************************************/
74
75 static BOOL pw_file_lock(int fd, int type, int secs, int *plock_depth)
76 {
77   if (fd < 0)
78     return False;
79
80   (*plock_depth)++;
81
82   if(pw_file_lock_depth == 0) {
83     if (!do_pw_lock(fd, secs, type)) {
84       DEBUG(10,("pw_file_lock: locking file failed, error = %s.\n",
85                  strerror(errno)));
86       return False;
87     }
88   }
89
90   return True;
91 }
92
93 /***************************************************************
94  Unlock an fd. Abandon after waitsecs seconds.
95 ****************************************************************/
96
97 static BOOL pw_file_unlock(int fd, int *plock_depth)
98 {
99   BOOL ret;
100
101   if(*plock_depth == 1)
102     ret = do_pw_lock(fd, 5, F_UNLCK);
103
104   (*plock_depth)--;
105
106   if(!ret)
107     DEBUG(10,("pw_file_unlock: unlocking file failed, error = %s.\n",
108                  strerror(errno)));
109   return ret;
110 }
111
112 /***************************************************************
113  Start to enumerate the smbpasswd list. Returns a void pointer
114  to ensure no modification outside this module.
115
116  do not call this function directly.  use passdb.c instead.
117
118  ****************************************************************/
119 void *startsmbpwent(BOOL update)
120 {
121   FILE *fp = NULL;
122   char *pfile = lp_smb_passwd_file();
123
124   if (!*pfile) {
125     DEBUG(0, ("startsmbpwent: No SMB password file set\n"));
126     return (NULL);
127   }
128   DEBUG(10, ("startsmbpwent: opening file %s\n", pfile));
129
130   fp = fopen(pfile, update ? "r+b" : "rb");
131
132   if (fp == NULL) {
133     DEBUG(0, ("startsmbpwent: unable to open file %s\n", pfile));
134     return NULL;
135   }
136
137   /* Set a 16k buffer to do more efficient reads */
138   setvbuf(fp, s_readbuf, _IOFBF, sizeof(s_readbuf));
139
140   if (!pw_file_lock(fileno(fp), (update ? F_WRLCK : F_RDLCK), 5, &pw_file_lock_depth))
141   {
142     DEBUG(0, ("startsmbpwent: unable to lock file %s\n", pfile));
143     fclose(fp);
144     return NULL;
145   }
146
147   /* Make sure it is only rw by the owner */
148   chmod(pfile, 0600);
149
150   /* We have a lock on the file. */
151   return (void *)fp;
152 }
153
154 /***************************************************************
155  End enumeration of the smbpasswd list.
156 ****************************************************************/
157
158 void endsmbpwent(void *vp)
159 {
160   FILE *fp = (FILE *)vp;
161
162   pw_file_unlock(fileno(fp), &pw_file_lock_depth);
163   fclose(fp);
164   DEBUG(7, ("endsmbpwent: closed password file.\n"));
165 }
166
167 /*************************************************************************
168  Routine to return the next entry in the smbpasswd list.
169
170  do not call this function directly.  use passdb.c instead.
171
172  *************************************************************************/
173 struct smb_passwd *getsmbpwent(void *vp)
174 {
175   /* Static buffers we will return. */
176   static struct smb_passwd pw_buf;
177   static pstring  user_name;
178   static unsigned char smbpwd[16];
179   static unsigned char smbntpwd[16];
180   FILE *fp = (FILE *)vp;
181   char            linebuf[256];
182   unsigned char   c;
183   unsigned char  *p;
184   long            uidval;
185   long            linebuf_len;
186
187   if(fp == NULL) {
188     DEBUG(0,("getsmbpwent: Bad password file pointer.\n"));
189     return NULL;
190   }
191
192   pw_buf.acct_ctrl = ACB_NORMAL;  
193   pw_buf.pass_last_set_time = (time_t)-1;
194
195   /*
196    * Scan the file, a line at a time and check if the name matches.
197    */
198   while (!feof(fp)) {
199     linebuf[0] = '\0';
200
201     fgets(linebuf, 256, fp);
202     if (ferror(fp)) {
203       return NULL;
204     }
205
206     /*
207      * Check if the string is terminated with a newline - if not
208      * then we must keep reading and discard until we get one.
209      */
210     linebuf_len = strlen(linebuf);
211     if (linebuf[linebuf_len - 1] != '\n') {
212       c = '\0';
213       while (!ferror(fp) && !feof(fp)) {
214         c = fgetc(fp);
215         if (c == '\n')
216           break;
217       }
218     } else
219       linebuf[linebuf_len - 1] = '\0';
220
221 #ifdef DEBUG_PASSWORD
222     DEBUG(100, ("getsmbpwent: got line |%s|\n", linebuf));
223 #endif
224     if ((linebuf[0] == 0) && feof(fp)) {
225       DEBUG(4, ("getsmbpwent: end of file reached\n"));
226       break;
227     }
228     /*
229      * The line we have should be of the form :-
230      * 
231      * username:uid:32hex bytes:[Account type]:LCT-12345678....other flags presently
232      * ignored....
233      * 
234      * or,
235      *
236      * username:uid:32hex bytes:32hex bytes:[Account type]:LCT-12345678....ignored....
237      *
238      * if Windows NT compatible passwords are also present.
239      * [Account type] is an ascii encoding of the type of account.
240      * LCT-(8 hex digits) is the time_t value of the last change time.
241      */
242
243     if (linebuf[0] == '#' || linebuf[0] == '\0') {
244       DEBUG(6, ("getsmbpwent: skipping comment or blank line\n"));
245       continue;
246     }
247     p = (unsigned char *) strchr(linebuf, ':');
248     if (p == NULL) {
249       DEBUG(0, ("getsmbpwent: malformed password entry (no :)\n"));
250       continue;
251     }
252     /*
253      * As 256 is shorter than a pstring we don't need to check
254      * length here - if this ever changes....
255      */
256     strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
257     user_name[PTR_DIFF(p, linebuf)] = '\0';
258
259     /* Get smb uid. */
260
261     p++;                /* Go past ':' */
262     if (!isdigit(*p)) {
263       DEBUG(0, ("getsmbpwent: malformed password entry (uid not number)\n"));
264       continue;
265     }
266
267     uidval = atoi((char *) p);
268
269     while (*p && isdigit(*p))
270       p++;
271
272     if (*p != ':') {
273       DEBUG(0, ("getsmbpwent: malformed password entry (no : after uid)\n"));
274       continue;
275     }
276
277     pw_buf.smb_name = user_name;
278     pw_buf.smb_userid = uidval;
279
280     /*
281      * Now get the password value - this should be 32 hex digits
282      * which are the ascii representations of a 16 byte string.
283      * Get two at a time and put them into the password.
284      */
285
286     /* Skip the ':' */
287     p++;
288
289     if (*p == '*' || *p == 'X') {
290       /* Password deliberately invalid - end here. */
291       DEBUG(10, ("getsmbpwent: entry invalidated for user %s\n", user_name));
292       pw_buf.smb_nt_passwd = NULL;
293       pw_buf.smb_passwd = NULL;
294       pw_buf.acct_ctrl |= ACB_DISABLED;
295       return &pw_buf;
296     }
297
298     if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) {
299       DEBUG(0, ("getsmbpwent: malformed password entry (passwd too short)\n"));
300       continue;
301     }
302
303     if (p[32] != ':') {
304       DEBUG(0, ("getsmbpwent: malformed password entry (no terminating :)\n"));
305       continue;
306     }
307
308     if (!strncasecmp((char *) p, "NO PASSWORD", 11)) {
309       pw_buf.smb_passwd = NULL;
310       pw_buf.acct_ctrl |= ACB_PWNOTREQ;
311     } else {
312       if (!gethexpwd((char *)p, (char *)smbpwd)) {
313         DEBUG(0, ("getsmbpwent: Malformed Lanman password entry (non hex chars)\n"));
314         continue;
315       }
316       pw_buf.smb_passwd = smbpwd;
317     }
318
319     /* 
320      * Now check if the NT compatible password is
321      * available.
322      */
323     pw_buf.smb_nt_passwd = NULL;
324
325     p += 33; /* Move to the first character of the line after
326                 the lanman password. */
327     if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
328       if (*p != '*' && *p != 'X') {
329         if(gethexpwd((char *)p,(char *)smbntpwd))
330           pw_buf.smb_nt_passwd = smbntpwd;
331       }
332       p += 33; /* Move to the first character of the line after
333                   the NT password. */
334     }
335
336     DEBUG(5, ("getsmbpwent: returning passwd entry for user %s, uid %d\n",
337                           user_name, uidval));
338
339     /*
340      * Check if the account type bits have been encoded after the
341      * NT password (in the form [NDHTUWSLXI]).
342      */
343
344     if (*p == '[') {
345       BOOL finished = False;
346
347       pw_buf.acct_ctrl = 0;
348
349       for(p++;*p && !finished; p++) {
350         switch (*p) {
351 #if 0
352    /*
353     * Hmmm. Don't allow these to be set/read independently
354     * of the actual password fields. We don't want a mismatch.
355     * JRA.
356     */
357           case 'N':
358             /* 'N'o password. */
359             pw_buf.acct_ctrl |= ACB_PWNOTREQ;
360             break;
361           case 'D':
362             /* 'D'isabled. */
363             pw_buf.acct_ctrl |= ACB_DISABLED;
364             break;
365 #endif 
366           case 'H':
367             /* 'H'omedir required. */
368             pw_buf.acct_ctrl |= ACB_HOMDIRREQ;
369             break;
370           case 'T':
371             /* 'T'emp account. */
372             pw_buf.acct_ctrl |= ACB_TEMPDUP;
373             break;
374           case 'U':
375             /* 'U'ser account (normal). */
376             pw_buf.acct_ctrl |= ACB_NORMAL;
377             break;
378           case 'M':
379             /* 'M'NS logon user account. What is this ? */
380             pw_buf.acct_ctrl |= ACB_MNS;
381             break;
382           case 'W':
383             /* 'W'orkstation account. */
384             pw_buf.acct_ctrl |= ACB_WSTRUST;
385             break;
386           case 'S':
387             /* 'S'erver account. */
388             pw_buf.acct_ctrl |= ACB_SVRTRUST;
389             break;
390           case 'L':
391             /* 'L'ocked account. */
392             pw_buf.acct_ctrl |= ACB_AUTOLOCK;
393             break;
394           case 'X':
395             /* No 'X'piry. */
396             pw_buf.acct_ctrl |= ACB_PWNOEXP;
397             break;
398           case 'I':
399             /* 'I'nterdomain trust account. */
400             pw_buf.acct_ctrl |= ACB_DOMTRUST;
401             break;
402
403           case ':':
404           case '\n':
405           case '\0': 
406           case ']':
407           default:
408             finished = True;
409         }
410       }
411
412       /* Must have some account type set. */
413       if(pw_buf.acct_ctrl == 0)
414         pw_buf.acct_ctrl = ACB_NORMAL;
415
416       /* Now try and get the last change time. */
417       if(*p == ']')
418         p++;
419       if(*p == ':') {
420         p++;
421         if(*p && StrnCaseCmp( p, "LCT-", 4)) {
422           int i;
423           p += 4;
424           for(i = 0; i < 8; i++) {
425             if(p[i] == '\0' || !isxdigit(p[i]))
426               break;
427           }
428           if(i == 8) {
429             /*
430              * p points at 8 characters of hex digits - 
431              * read into a time_t as the seconds since
432              * 1970 that the password was last changed.
433              */
434             pw_buf.pass_last_set_time = (time_t)strtol(p, NULL, 16);
435           }
436         }
437       }
438     } else {
439       /* 'Old' style file. Fake up based on user name. */
440       /*
441        * Currently machine accounts are kept in the same
442        * password file as 'normal accounts'. If this changes
443        * we will have to fix this code. JRA.
444        */
445       if(pw_buf.smb_name[strlen(pw_buf.smb_name) - 1] == '$') {
446         pw_buf.acct_ctrl &= ~ACB_NORMAL;
447         pw_buf.acct_ctrl |= ACB_WSTRUST;
448       }
449     }
450
451     return &pw_buf;
452   }
453
454   DEBUG(5,("getsmbpwent: end of file reached.\n"));
455   return NULL;
456 }
457
458 /*************************************************************************
459  Return the current position in the smbpasswd list as an unsigned long.
460  This must be treated as an opaque token.
461
462  do not call this function directly.  use passdb.c instead.
463
464 *************************************************************************/
465 unsigned long getsmbpwpos(void *vp)
466 {
467   return (unsigned long)ftell((FILE *)vp);
468 }
469
470 /*************************************************************************
471  Set the current position in the smbpasswd list from unsigned long.
472  This must be treated as an opaque token.
473
474  do not call this function directly.  use passdb.c instead.
475
476 *************************************************************************/
477 BOOL setsmbpwpos(void *vp, unsigned long tok)
478 {
479   return !fseek((FILE *)vp, tok, SEEK_SET);
480 }
481
482 /*************************************************************************
483  Routine to search the smbpasswd file for an entry matching the username
484  or user id.  if the name is NULL, then the smb_uid is used instead.
485  *************************************************************************/
486 static struct smb_passwd *get_smbpwd_entry(char *name, int smb_userid)
487 {
488   struct smb_passwd *pwd = NULL;
489   FILE *fp = NULL;
490
491   if (name != NULL) {
492     DEBUG(10, ("get_smbpwd_entry: search by name: %s\n", name));
493   } else {
494     DEBUG(10, ("get_smbpwd_entry: search by smb_userid: %x\n", smb_userid));
495   }
496
497   /* Open the smbpassword file - not for update. */
498   fp = startsmbpwent(False);
499
500   if (fp == NULL) {
501     DEBUG(0, ("get_smbpwd_entry: unable to open password file.\n"));
502     return NULL;
503   }
504
505   /*
506    * Scan the file, a line at a time and check if the name 
507    * or uid matches.
508    */
509
510   while ((pwd = getsmbpwent(fp)) != NULL) {
511     if (name != NULL) {
512       /* Search is by user name */
513       if (!strequal(pwd->smb_name, name))
514         continue;
515       DEBUG(10, ("get_smbpwd_entry: found by name: %s\n", name));
516       break;
517     } else {
518       /* Search is by user id */
519       if (pwd->smb_userid != smb_userid)
520         continue;
521       DEBUG(10, ("get_smbpwd_entry: found by smb_userid: %x\n", smb_userid));
522       break;
523     }
524   }
525
526   endsmbpwent(fp);
527   return pwd;
528 }
529
530 /************************************************************************
531  Routine to search smb passwd by name.
532 *************************************************************************/
533
534 struct smb_passwd *getsmbpwnam(char *name)
535 {
536   return get_smbpwd_entry(name, 0);
537 }
538
539
540 /************************************************************************
541  Routine to search smb passwd by uid.
542 *************************************************************************/
543
544 struct smb_passwd *getsmbpwuid(unsigned int uid)
545 {
546   return get_smbpwd_entry(NULL, uid);
547 }
548
549
550 /**********************************************************
551  Encode the account control bits into a string.
552 **********************************************************/
553         
554 char *encode_acct_ctrl(uint16 acct_ctrl)
555 {
556   static fstring acct_str;
557   char *p = acct_str;
558  
559   *p++ = '[';
560
561   if (acct_ctrl & ACB_HOMDIRREQ) *p++ = 'H';
562   if (acct_ctrl & ACB_TEMPDUP  ) *p++ = 'T'; 
563   if (acct_ctrl & ACB_NORMAL   ) *p++ = 'U';
564   if (acct_ctrl & ACB_MNS      ) *p++ = 'M';
565   if (acct_ctrl & ACB_WSTRUST  ) *p++ = 'W';
566   if (acct_ctrl & ACB_SVRTRUST ) *p++ = 'S';
567   if (acct_ctrl & ACB_AUTOLOCK ) *p++ = 'L';
568   if (acct_ctrl & ACB_PWNOEXP  ) *p++ = 'X';
569   if (acct_ctrl & ACB_DOMTRUST ) *p++ = 'I';
570       
571   *p++ = ']';
572   *p = '\0';
573   return acct_str;
574 }     
575
576 /************************************************************************
577  Routine to add an entry to the smbpasswd file.
578
579  do not call this function directly.  use passdb.c instead.
580
581 *************************************************************************/
582
583 BOOL add_smbpwd_entry(struct smb_passwd *newpwd)
584 {
585   char *pfile = lp_smb_passwd_file();
586   struct smb_passwd *pwd = NULL;
587   FILE *fp = NULL;
588
589   int i;
590   int wr_len;
591
592   int fd;
593   int new_entry_length;
594   char *new_entry;
595   long offpos;
596   unsigned char *p;
597
598   /* Open the smbpassword file - for update. */
599   fp = startsmbpwent(True);
600
601   if (fp == NULL) {
602     DEBUG(0, ("add_smbpwd_entry: unable to open file.\n"));
603     return False;
604   }
605
606   /*
607    * Scan the file, a line at a time and check if the name matches.
608    */
609
610   while ((pwd = getsmbpwent(fp)) != NULL) {
611     if (strequal(newpwd->smb_name, pwd->smb_name)) {
612       DEBUG(0, ("add_smbpwd_entry: entry with name %s already exists\n", pwd->smb_name));
613       endsmbpwent(fp);
614       return False;
615     }
616   }
617
618   /* Ok - entry doesn't exist. We can add it */
619
620   /* Create a new smb passwd entry and set it to the given password. */
621   /* 
622    * The add user write needs to be atomic - so get the fd from 
623    * the fp and do a raw write() call.
624    */
625   fd = fileno(fp);
626
627   if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
628     DEBUG(0, ("add_smbpwd_entry(lseek): Failed to add entry for user %s to file %s. \
629 Error was %s\n", newpwd->smb_name, pfile, strerror(errno)));
630     endsmbpwent(fp);
631     return False;
632   }
633
634   new_entry_length = strlen(newpwd->smb_name) + 1 + 15 + 1 + 32 + 1 + 32 + 1 + 5 + 1 + 13 + 2;
635
636   if((new_entry = (char *)malloc( new_entry_length )) == NULL) {
637     DEBUG(0, ("add_smbpwd_entry(malloc): Failed to add entry for user %s to file %s. \
638 Error was %s\n", newpwd->smb_name, pfile, strerror(errno)));
639     endsmbpwent(fp);
640     return False;
641   }
642
643   sprintf(new_entry, "%s:%u:", newpwd->smb_name, (unsigned)newpwd->smb_userid);
644   p = (unsigned char *)&new_entry[strlen(new_entry)];
645
646   if(newpwd->smb_passwd != NULL) {
647     for( i = 0; i < 16; i++) {
648       sprintf((char *)&p[i*2], "%02X", newpwd->smb_passwd[i]);
649     }
650   } else {
651     i=0;
652     if(newpwd->acct_ctrl & ACB_PWNOTREQ)
653       sprintf(p, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX");
654     else
655       sprintf(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
656   }
657   
658   p += 32;
659
660   *p++ = ':';
661
662   if(newpwd->smb_nt_passwd != NULL) {
663     for( i = 0; i < 16; i++) {
664       sprintf((char *)&p[i*2], "%02X", newpwd->smb_nt_passwd[i]);
665     }
666   } else {
667     if(newpwd->acct_ctrl & ACB_PWNOTREQ)
668       sprintf(p, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX");
669     else
670       sprintf(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
671   }
672
673   p += 32;
674
675   *p++ = ':';
676
677   /* Add the account encoding and the last change time. */
678   sprintf((char *)p, "%s:LCT-%08X:\n", encode_acct_ctrl(newpwd->acct_ctrl),
679                      (uint32)time(NULL));
680
681 #ifdef DEBUG_PASSWORD
682   DEBUG(100, ("add_smbpwd_entry(%d): new_entry_len %d entry_len %d made line |%s|", 
683                              fd, new_entry_length, strlen(new_entry), new_entry));
684 #endif
685
686   if ((wr_len = write(fd, new_entry, strlen(new_entry))) != strlen(new_entry)) {
687     DEBUG(0, ("add_smbpwd_entry(write): %d Failed to add entry for user %s to file %s. \
688 Error was %s\n", wr_len, newpwd->smb_name, pfile, strerror(errno)));
689
690     /* Remove the entry we just wrote. */
691     if(ftruncate(fd, offpos) == -1) {
692       DEBUG(0, ("add_smbpwd_entry: ERROR failed to ftruncate file %s. \
693 Error was %s. Password file may be corrupt ! Please examine by hand !\n", 
694              newpwd->smb_name, strerror(errno)));
695     }
696
697     endsmbpwent(fp);
698     return False;
699   }
700
701   endsmbpwent(fp);
702   return True;
703 }
704
705 /************************************************************************
706  Routine to search the smbpasswd file for an entry matching the username.
707  and then modify its password entry. We can't use the startsmbpwent()/
708  getsmbpwent()/endsmbpwent() interfaces here as we depend on looking
709  in the actual file to decide how much room we have to write data.
710  override = False, normal
711  override = True, override XXXXXXXX'd out password or NO PASS
712
713  do not call this function directly.  use passdb.c instead.
714
715 ************************************************************************/
716
717 BOOL mod_smbpwd_entry(struct smb_passwd* pwd, BOOL override)
718 {
719   /* Static buffers we will return. */
720   static pstring  user_name;
721
722   char            linebuf[256];
723   char            readbuf[16 * 1024];
724   unsigned char   c;
725   fstring         ascii_p16;
726   fstring         encode_bits;
727   unsigned char  *p = NULL;
728   long            linebuf_len = 0;
729   FILE           *fp;
730   int             lockfd;
731   char           *pfile = lp_smb_passwd_file();
732   BOOL found_entry = False;
733   BOOL got_pass_last_set_time = False;
734
735   long pwd_seekpos = 0;
736
737   int i;
738   int wr_len;
739   int fd;
740
741   if (!*pfile) {
742     DEBUG(0, ("No SMB password file set\n"));
743     return False;
744   }
745   DEBUG(10, ("mod_smbpwd_entry: opening file %s\n", pfile));
746
747   fp = fopen(pfile, "r+");
748
749   if (fp == NULL) {
750     DEBUG(0, ("mod_smbpwd_entry: unable to open file %s\n", pfile));
751     return False;
752   }
753   /* Set a 16k buffer to do more efficient reads */
754   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
755
756   lockfd = fileno(fp);
757
758   if (!pw_file_lock(lockfd, F_WRLCK, 5, &pw_file_lock_depth)) {
759     DEBUG(0, ("mod_smbpwd_entry: unable to lock file %s\n", pfile));
760     fclose(fp);
761     return False;
762   }
763
764   /* Make sure it is only rw by the owner */
765   chmod(pfile, 0600);
766
767   /* We have a write lock on the file. */
768   /*
769    * Scan the file, a line at a time and check if the name matches.
770    */
771   while (!feof(fp)) {
772     pwd_seekpos = ftell(fp);
773
774     linebuf[0] = '\0';
775
776     fgets(linebuf, sizeof(linebuf), fp);
777     if (ferror(fp)) {
778       pw_file_unlock(lockfd, &pw_file_lock_depth);
779       fclose(fp);
780       return False;
781     }
782
783     /*
784      * Check if the string is terminated with a newline - if not
785      * then we must keep reading and discard until we get one.
786      */
787     linebuf_len = strlen(linebuf);
788     if (linebuf[linebuf_len - 1] != '\n') {
789       c = '\0';
790       while (!ferror(fp) && !feof(fp)) {
791         c = fgetc(fp);
792         if (c == '\n') {
793           break;
794         }
795       }
796     } else {
797       linebuf[linebuf_len - 1] = '\0';
798     }
799
800 #ifdef DEBUG_PASSWORD
801     DEBUG(100, ("mod_smbpwd_entry: got line |%s|\n", linebuf));
802 #endif
803
804     if ((linebuf[0] == 0) && feof(fp)) {
805       DEBUG(4, ("mod_smbpwd_entry: end of file reached\n"));
806       break;
807     }
808
809     /*
810      * The line we have should be of the form :-
811      * 
812      * username:uid:[32hex bytes]:....other flags presently
813      * ignored....
814      * 
815      * or,
816      *
817      * username:uid:[32hex bytes]:[32hex bytes]:....ignored....
818      *
819      * if Windows NT compatible passwords are also present.
820      */
821
822     if (linebuf[0] == '#' || linebuf[0] == '\0') {
823       DEBUG(6, ("mod_smbpwd_entry: skipping comment or blank line\n"));
824       continue;
825     }
826
827     p = (unsigned char *) strchr(linebuf, ':');
828
829     if (p == NULL) {
830       DEBUG(0, ("mod_smbpwd_entry: malformed password entry (no :)\n"));
831       continue;
832     }
833
834     /*
835      * As 256 is shorter than a pstring we don't need to check
836      * length here - if this ever changes....
837      */
838     strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
839     user_name[PTR_DIFF(p, linebuf)] = '\0';
840     if (strequal(user_name, pwd->smb_name)) {
841       found_entry = True;
842       break;
843     }
844   }
845
846   if (!found_entry) return False;
847
848   DEBUG(6, ("mod_smbpwd_entry: entry exists\n"));
849
850   /* User name matches - get uid and password */
851   p++;          /* Go past ':' */
852
853   if (!isdigit(*p)) {
854     DEBUG(0, ("mod_smbpwd_entry: malformed password entry (uid not number)\n"));
855     pw_file_unlock(lockfd, &pw_file_lock_depth);
856     fclose(fp);
857     return False;
858   }
859
860   while (*p && isdigit(*p))
861     p++;
862   if (*p != ':') {
863     DEBUG(0, ("mod_smbpwd_entry: malformed password entry (no : after uid)\n"));
864     pw_file_unlock(lockfd, &pw_file_lock_depth);
865     fclose(fp);
866     return False;
867   }
868
869   /*
870    * Now get the password value - this should be 32 hex digits
871    * which are the ascii representations of a 16 byte string.
872    * Get two at a time and put them into the password.
873    */
874   p++;
875
876   /* Record exact password position */
877   pwd_seekpos += PTR_DIFF(p, linebuf);
878
879   if (!override && (*p == '*' || *p == 'X')) {
880     /* Password deliberately invalid - end here. */
881     DEBUG(10, ("mod_smbpwd_entry: entry invalidated for user %s\n", user_name));
882     pw_file_unlock(lockfd, &pw_file_lock_depth);
883     fclose(fp);
884     return False;
885   }
886
887   if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) {
888     DEBUG(0, ("mod_smbpwd_entry: malformed password entry (passwd too short)\n"));
889     pw_file_unlock(lockfd,&pw_file_lock_depth);
890     fclose(fp);
891     return (False);
892   }
893
894   if (p[32] != ':') {
895     DEBUG(0, ("mod_smbpwd_entry: malformed password entry (no terminating :)\n"));
896     pw_file_unlock(lockfd,&pw_file_lock_depth);
897     fclose(fp);
898     return False;
899   }
900
901   if (!override && (*p == '*' || *p == 'X')) {
902     pw_file_unlock(lockfd,&pw_file_lock_depth);
903     fclose(fp);
904     return False;
905   }
906
907   /* Now check if the NT compatible password is
908      available. */
909   p += 33; /* Move to the first character of the line after
910               the lanman password. */
911   if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) {
912     DEBUG(0, ("mod_smbpwd_entry: malformed password entry (passwd too short)\n"));
913     pw_file_unlock(lockfd,&pw_file_lock_depth);
914     fclose(fp);
915     return (False);
916   }
917
918   if (p[32] != ':') {
919     DEBUG(0, ("mod_smbpwd_entry: malformed password entry (no terminating :)\n"));
920     pw_file_unlock(lockfd,&pw_file_lock_depth);
921     fclose(fp);
922     return False;
923   }
924
925   /* 
926    * Now check if the account info and the password last
927    * change time is available.
928    */
929   p += 33; /* Move to the first character of the line after
930               the NT password. */
931
932   if (*p == '[') {
933
934     i = 0;
935     p++;
936     while((linebuf_len > PTR_DIFF(p, linebuf)) && (*p != ']'))
937       encode_bits[i++] = *p++;
938
939     encode_bits[i] = '\0';
940
941     /* Go past the ']' */
942     if(linebuf_len > PTR_DIFF(p, linebuf))
943       p++;
944
945     if((linebuf_len > PTR_DIFF(p, linebuf)) && (*p == ':')) {
946       p++;
947
948       /* We should be pointing at the TLC entry. */
949       if((linebuf_len > (PTR_DIFF(p, linebuf) + 13)) && StrnCaseCmp( p, "LCT-", 4)) {
950
951         p += 4;
952         for(i = 0; i < 8; i++) {
953           if(p[i] == '\0' || !isxdigit(p[i]))
954             break;
955         }
956         if(i == 8) {
957           /*
958            * p points at 8 characters of hex digits -
959            * read into a time_t as the seconds since
960            * 1970 that the password was last changed.
961            */
962           got_pass_last_set_time = True;
963         } /* i == 8 */
964       } /* *p && StrnCaseCmp() */
965     } /* p == ':' */
966   } /* p == '[' */
967
968   /* Entry is correctly formed. */
969
970   /*
971    * Do an atomic write into the file at the position defined by
972    * seekpos.
973    */
974
975   /* The mod user write needs to be atomic - so get the fd from 
976      the fp and do a raw write() call.
977    */
978
979   fd = fileno(fp);
980
981   if (lseek(fd, pwd_seekpos - 1, SEEK_SET) != pwd_seekpos - 1) {
982     DEBUG(0, ("mod_smbpwd_entry: seek fail on file %s.\n", pfile));
983     pw_file_unlock(lockfd,&pw_file_lock_depth);
984     fclose(fp);
985     return False;
986   }
987
988   /* Sanity check - ensure the character is a ':' */
989   if (read(fd, &c, 1) != 1) {
990     DEBUG(0, ("mod_smbpwd_entry: read fail on file %s.\n", pfile));
991     pw_file_unlock(lockfd,&pw_file_lock_depth);
992     fclose(fp);
993     return False;
994   }
995
996   if (c != ':') {
997     DEBUG(0, ("mod_smbpwd_entry: check on passwd file %s failed.\n", pfile));
998     pw_file_unlock(lockfd,&pw_file_lock_depth);
999     fclose(fp);
1000     return False;
1001   }
1002  
1003   /* Create the 32 byte representation of the new p16 */
1004   if(pwd->smb_passwd != NULL) {
1005     for (i = 0; i < 16; i++) {
1006       sprintf(&ascii_p16[i*2], "%02X", (uchar) pwd->smb_passwd[i]);
1007     }
1008   } else {
1009     if(pwd->acct_ctrl & ACB_PWNOTREQ)
1010       sprintf(ascii_p16, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX");
1011     else
1012       sprintf(ascii_p16, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
1013   }
1014
1015   /* Add on the NT md4 hash */
1016   ascii_p16[32] = ':';
1017   wr_len = 65;
1018   if (pwd->smb_nt_passwd != NULL) {
1019     for (i = 0; i < 16; i++) {
1020       sprintf(&ascii_p16[(i*2)+33], "%02X", (uchar) pwd->smb_nt_passwd[i]);
1021     }
1022   } else {
1023     if(pwd->acct_ctrl & ACB_PWNOTREQ)
1024       sprintf(&ascii_p16[33], "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX");
1025     else
1026       sprintf(&ascii_p16[33], "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
1027   }
1028
1029   /* Add on the account info bits and the time of last
1030      password change. */
1031
1032   pwd->pass_last_set_time = time(NULL);
1033
1034   if(got_pass_last_set_time) {
1035     sprintf(&ascii_p16[strlen(ascii_p16)], ":[%s]:TLC-%08X:", 
1036                      encode_bits, (uint32)pwd->pass_last_set_time );
1037     wr_len = strlen(ascii_p16);
1038   }
1039
1040 #ifdef DEBUG_PASSWORD
1041   DEBUG(100,("mod_smbpwd_entry: "));
1042   dump_data(100, ascii_p16, wr_len);
1043 #endif
1044
1045   if (write(fd, ascii_p16, wr_len) != wr_len) {
1046     DEBUG(0, ("mod_smbpwd_entry: write failed in passwd file %s\n", pfile));
1047     pw_file_unlock(lockfd,&pw_file_lock_depth);
1048     fclose(fp);
1049     return False;
1050   }
1051
1052   pw_file_unlock(lockfd,&pw_file_lock_depth);
1053   fclose(fp);
1054   return True;
1055 }
1056
1057 static int mach_passwd_lock_depth;
1058 static FILE *mach_passwd_fp;
1059
1060 /************************************************************************
1061  Routine to get the name for a machine account file.
1062 ************************************************************************/
1063
1064 static void get_machine_account_file_name( char *domain, char *name, char *mac_file)
1065 {
1066   unsigned int mac_file_len;
1067   char *p;
1068
1069   pstrcpy(mac_file, lp_smb_passwd_file());
1070   p = strrchr(mac_file, '/');
1071   if(p != NULL)
1072     *++p = '\0';
1073
1074   mac_file_len = strlen(mac_file);
1075
1076   if (sizeof(pstring) - mac_file_len - strlen(domain) - strlen(name) - 6 < 0)
1077   {
1078     DEBUG(0,("machine_password_lock: path %s too long to add machine details.\n",
1079               mac_file));
1080     return;
1081   }
1082
1083   strcat(mac_file, domain);
1084   strcat(mac_file, ".");
1085   strcat(mac_file, name);
1086   strcat(mac_file, ".mac");
1087 }
1088  
1089 /************************************************************************
1090  Routine to lock the machine account password file for a domain.
1091 ************************************************************************/
1092
1093 BOOL machine_password_lock( char *domain, char *name, BOOL update)
1094 {
1095   pstring mac_file;
1096
1097   if(mach_passwd_lock_depth == 0) {
1098
1099     get_machine_account_file_name( domain, name, mac_file);
1100
1101     if((mach_passwd_fp = fopen(mac_file, "r+b")) == NULL) {
1102       if(errno == ENOENT && update) {
1103         mach_passwd_fp = fopen(mac_file, "w+b");
1104       }
1105
1106       if(mach_passwd_fp == NULL) {
1107         DEBUG(0,("machine_password_lock: cannot open file %s - Error was %s.\n",
1108               mac_file, strerror(errno) ));
1109         return False;
1110       }
1111     }
1112
1113     chmod(mac_file, 0600);
1114
1115     if(!pw_file_lock(fileno(mach_passwd_fp), (update ? F_WRLCK : F_RDLCK), 
1116                                       60, &mach_passwd_lock_depth))
1117     {
1118       DEBUG(0,("machine_password_lock: cannot lock file %s\n", mac_file));
1119       fclose(mach_passwd_fp);
1120       return False;
1121     }
1122
1123   }
1124
1125   return True;
1126 }
1127
1128 /************************************************************************
1129  Routine to unlock the machine account password file for a domain.
1130 ************************************************************************/
1131
1132 BOOL machine_password_unlock(void)
1133 {
1134   BOOL ret = pw_file_unlock(fileno(mach_passwd_fp), &mach_passwd_lock_depth);
1135   if(mach_passwd_lock_depth == 0)
1136     fclose(mach_passwd_fp);
1137   return ret;
1138 }
1139
1140 /************************************************************************
1141  Routine to delete the machine account password file for a domain.
1142 ************************************************************************/
1143
1144 BOOL machine_password_delete( char *domain, char *name )
1145 {
1146   pstring mac_file;
1147
1148   get_machine_account_file_name( domain, name, mac_file);
1149   return (unlink( mac_file ) == 0);
1150 }
1151
1152 /************************************************************************
1153  Routine to get the machine account password for a domain.
1154  The user of this function must have locked the machine password file.
1155 ************************************************************************/
1156
1157 BOOL get_machine_account_password( unsigned char *ret_pwd, time_t *pass_last_set_time)
1158 {
1159   char linebuf[256];
1160   char *p;
1161   int i;
1162
1163   linebuf[0] = '\0';
1164
1165   *pass_last_set_time = (time_t)0;
1166   memset(ret_pwd, '\0', 16);
1167
1168   if(fseek( mach_passwd_fp, 0L, SEEK_SET) == -1) {
1169     DEBUG(0,("get_machine_account_password: Failed to seek to start of file. Error was %s.\n",
1170               strerror(errno) ));
1171     return False;
1172   } 
1173
1174   fgets(linebuf, sizeof(linebuf), mach_passwd_fp);
1175   if(ferror(mach_passwd_fp)) {
1176     DEBUG(0,("get_machine_account_password: Failed to read password. Error was %s.\n",
1177               strerror(errno) ));
1178     return False;
1179   }
1180
1181   /*
1182    * The length of the line read
1183    * must be 45 bytes ( <---XXXX 32 bytes-->:TLC-12345678
1184    */
1185
1186   if(strlen(linebuf) != 45) {
1187     DEBUG(0,("get_machine_account_password: Malformed machine password file (wrong length).\n"));
1188 #ifdef DEBUG_PASSWORD
1189     DEBUG(100,("get_machine_account_password: line = |%s|\n", linebuf));
1190 #endif
1191     return False;
1192   }
1193
1194   /*
1195    * Get the hex password.
1196    */
1197
1198   if (!gethexpwd((char *)linebuf, (char *)ret_pwd) || linebuf[32] != ':' || 
1199          strncmp(&linebuf[33], "TLC-", 4)) {
1200     DEBUG(0,("get_machine_account_password: Malformed machine password file (incorrect format).\n"));
1201 #ifdef DEBUG_PASSWORD
1202     DEBUG(100,("get_machine_account_password: line = |%s|\n", linebuf));
1203 #endif
1204     return False;
1205   }
1206
1207   /*
1208    * Get the last changed time.
1209    */
1210   p = &linebuf[37];
1211
1212   for(i = 0; i < 8; i++) {
1213     if(p[i] == '\0' || !isxdigit(p[i])) {
1214       DEBUG(0,("get_machine_account_password: Malformed machine password file (no timestamp).\n"));
1215 #ifdef DEBUG_PASSWORD
1216       DEBUG(100,("get_machine_account_password: line = |%s|\n", linebuf));
1217 #endif
1218       return False;
1219     }
1220   }
1221
1222   /*
1223    * p points at 8 characters of hex digits -
1224    * read into a time_t as the seconds since
1225    * 1970 that the password was last changed.
1226    */
1227
1228   *pass_last_set_time = (time_t)strtol(p, NULL, 16);
1229
1230   return True;
1231 }
1232
1233 /************************************************************************
1234  Routine to get the machine account password for a domain.
1235  The user of this function must have locked the machine password file.
1236 ************************************************************************/
1237
1238 BOOL set_machine_account_password( unsigned char *md4_new_pwd)
1239 {
1240   char linebuf[64];
1241   int i;
1242
1243   if(fseek( mach_passwd_fp, 0L, SEEK_SET) == -1) {
1244     DEBUG(0,("set_machine_account_password: Failed to seek to start of file. Error was %s.\n",
1245               strerror(errno) ));
1246     return False;
1247   } 
1248
1249   for (i = 0; i < 16; i++)
1250     sprintf(&linebuf[(i*2)], "%02X", md4_new_pwd[i]);
1251
1252   sprintf(&linebuf[32], ":TLC-%08X\n", (unsigned)time(NULL));
1253
1254   if(fwrite( linebuf, 1, 45, mach_passwd_fp)!= 45) {
1255     DEBUG(0,("set_machine_account_password: Failed to write file. Warning - the machine \
1256 machine account is now invalid. Please recreate. Error was %s.\n", strerror(errno) ));
1257     return False;
1258   }
1259
1260   fflush(mach_passwd_fp);
1261   return True;
1262 }