Added -a option (for Andrew:-). Fixed bug where users with
[samba.git] / source3 / utils / smbpasswd.c
1 #ifdef SMB_PASSWD
2
3 /*
4  * Unix SMB/Netbios implementation. Version 1.9. smbpasswd module. Copyright
5  * (C) Jeremy Allison 1995-1997.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  * 
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  * 
17  * You should have received a copy of the GNU General Public License along with
18  * this program; if not, write to the Free Software Foundation, Inc., 675
19  * Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "includes.h"
23 #include "des.h"
24
25 /* Static buffers we will return. */
26 static struct smb_passwd pw_buf;
27 static pstring  user_name;
28 static unsigned char smbpwd[16];
29 static unsigned char smbntpwd[16];
30
31 static int gethexpwd(char *p, char *pwd)
32 {
33         int i;
34         unsigned char   lonybble, hinybble;
35         char           *hexchars = "0123456789ABCDEF";
36         char           *p1, *p2;
37         for (i = 0; i < 32; i += 2) {
38                 hinybble = toupper(p[i]);
39                 lonybble = toupper(p[i + 1]);
40
41                 p1 = strchr(hexchars, hinybble);
42                 p2 = strchr(hexchars, lonybble);
43                 if (!p1 || !p2)
44                         return (False);
45
46                 hinybble = PTR_DIFF(p1, hexchars);
47                 lonybble = PTR_DIFF(p2, hexchars);
48
49                 pwd[i / 2] = (hinybble << 4) | lonybble;
50         }
51         return (True);
52 }
53
54 struct smb_passwd *
55 _my_get_smbpwnam(FILE * fp, char *name, BOOL * valid_old_pwd, 
56                 BOOL *got_valid_nt_entry, long *pwd_seekpos)
57 {
58         char            linebuf[256];
59         unsigned char   c;
60         unsigned char  *p;
61         long            uidval;
62         long            linebuf_len;
63
64         /*
65          * Scan the file, a line at a time and check if the name matches.
66          */
67         while (!feof(fp)) {
68                 linebuf[0] = '\0';
69                 *pwd_seekpos = ftell(fp);
70
71                 fgets(linebuf, 256, fp);
72                 if (ferror(fp))
73                         return NULL;
74
75                 /*
76                  * Check if the string is terminated with a newline - if not
77                  * then we must keep reading and discard until we get one.
78                  */
79                 linebuf_len = strlen(linebuf);
80                 if (linebuf[linebuf_len - 1] != '\n') {
81                         c = '\0';
82                         while (!ferror(fp) && !feof(fp)) {
83                                 c = fgetc(fp);
84                                 if (c == '\n')
85                                         break;
86                         }
87                 } else
88                         linebuf[linebuf_len - 1] = '\0';
89
90                 if ((linebuf[0] == 0) && feof(fp))
91                         break;
92                 /*
93                  * The line we have should be of the form :-
94                  * 
95                  * username:uid:[32hex bytes]:....other flags presently
96                  * ignored....
97                  * 
98                  * or,
99                  * 
100                  * username:uid:[32hex bytes]:[32hex bytes]:....ignored....
101                  * 
102                  * if Windows NT compatible passwords are also present.
103                  */
104
105                 if (linebuf[0] == '#' || linebuf[0] == '\0')
106                         continue;
107                 p = (unsigned char *) strchr(linebuf, ':');
108                 if (p == NULL)
109                         continue;
110                 /*
111                  * As 256 is shorter than a pstring we don't need to check
112                  * length here - if this ever changes....
113                  */
114                 strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
115                 user_name[PTR_DIFF(p, linebuf)] = '\0';
116                 if (!strequal(user_name, name))
117                         continue;
118
119                 /* User name matches - get uid and password */
120                 p++;            /* Go past ':' */
121                 if (!isdigit(*p))
122                         return (False);
123
124                 uidval = atoi((char *) p);
125                 while (*p && isdigit(*p))
126                         p++;
127
128                 if (*p != ':')
129                         return (False);
130
131                 /*
132                  * Now get the password value - this should be 32 hex digits
133                  * which are the ascii representations of a 16 byte string.
134                  * Get two at a time and put them into the password.
135                  */
136                 p++;
137                 *pwd_seekpos += PTR_DIFF(p, linebuf);   /* Save exact position
138                                                          * of passwd in file -
139                                                          * this is used by
140                                                          * smbpasswd.c */
141                 if (*p == '*' || *p == 'X') {
142                         /* Password deliberately invalid - end here. */
143                         *valid_old_pwd = False;
144                         *got_valid_nt_entry = False;
145                         pw_buf.smb_nt_passwd = NULL;    /* No NT password (yet)*/
146
147                         /* Now check if the NT compatible password is
148                            available. */
149                         p += 33; /* Move to the first character of the line after 
150                                                 the lanman password. */
151                         if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
152                                 /* NT Entry was valid - even if 'X' or '*', can be overwritten */
153                                 *got_valid_nt_entry = True;
154                                 if (*p != '*' && *p != 'X') {
155                                   if (gethexpwd((char *)p,(char *)smbntpwd))
156                                     pw_buf.smb_nt_passwd = smbntpwd;
157                                 }
158                         }
159                         pw_buf.smb_name = user_name;
160                         pw_buf.smb_userid = uidval;
161                         pw_buf.smb_passwd = NULL;       /* No password */
162                         return (&pw_buf);
163                 }
164                 if (linebuf_len < (PTR_DIFF(p, linebuf) + 33))
165                         return (False);
166
167                 if (p[32] != ':')
168                         return (False);
169
170                 if (!strncasecmp((char *)p, "NO PASSWORD", 11)) {
171                   pw_buf.smb_passwd = NULL;     /* No password */
172                 } else {
173                   if(!gethexpwd((char *)p,(char *)smbpwd))
174                     return False;
175                   pw_buf.smb_passwd = smbpwd;
176                 }
177
178                 pw_buf.smb_name = user_name;
179                 pw_buf.smb_userid = uidval;
180                 pw_buf.smb_nt_passwd = NULL;
181                 *got_valid_nt_entry = False;
182                 *valid_old_pwd = True;
183
184                 /* Now check if the NT compatible password is
185                    available. */
186                 p += 33; /* Move to the first character of the line after 
187                                         the lanman password. */
188                 if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
189                         /* NT Entry was valid - even if 'X' or '*', can be overwritten */
190                         *got_valid_nt_entry = True;
191                         if (*p != '*' && *p != 'X') {
192                           if (gethexpwd((char *)p,(char *)smbntpwd))
193                             pw_buf.smb_nt_passwd = smbntpwd;
194                         }
195                 }
196                 return &pw_buf;
197         }
198         return NULL;
199 }
200
201 /*
202  * Print command usage on stderr and die.
203  */
204 static void usage(char *name)
205 {
206         fprintf(stderr, "Usage is : %s [-add] [username]\n", name);
207         exit(1);
208 }
209
210  int main(int argc, char **argv)
211 {
212   int             real_uid;
213   struct passwd  *pwd;
214   fstring         old_passwd;
215   uchar           old_p16[16];
216   uchar           old_nt_p16[16];
217   fstring         new_passwd;
218   uchar           new_p16[16];
219   uchar           new_nt_p16[16];
220   char           *p;
221   struct smb_passwd *smb_pwent;
222   FILE           *fp;
223   BOOL            valid_old_pwd = False;
224   BOOL                  got_valid_nt_entry = False;
225   BOOL            add_user = False;
226   long            seekpos;
227   int             pwfd;
228   char            ascii_p16[66];
229   char            c;
230   int             ret, i, err, writelen;
231   int             lockfd = -1;
232   char           *pfile = SMB_PASSWD_FILE;
233   char            readbuf[16 * 1024];
234   
235   TimeInit();
236
237   setup_logging(argv[0],True);
238   
239   charset_initialise(0);
240   
241 #ifndef DEBUG_PASSWORD
242   /* Check the effective uid */
243   if (geteuid() != 0) {
244     fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
245     exit(1);
246   }
247 #endif
248   
249   /* Get the real uid */
250   real_uid = getuid();
251   
252   /* Deal with usage problems */
253   if (real_uid == 0) {
254     /* As root we can change anothers password and add a user. */
255     if (argc > 3 )
256       usage(argv[0]);
257   } else if (argc != 1) {
258     fprintf(stderr, "%s: Only root can set anothers password.\n", argv[0]);
259     usage(argv[0]);
260   }
261   
262   if (real_uid == 0 && (argc > 1)) {
263     /* We are root - check if we should add the user */
264     if ((argv[1][0] == '-') && (argv[1][1] == 'a'))
265       add_user = True;
266     if(add_user && (argc != 3))
267       usage(argv[0]);
268
269     /* If we are root we can change anothers password. */
270     strncpy(user_name, add_user ? argv[2] : argv[1], sizeof(user_name) - 1);
271     user_name[sizeof(user_name) - 1] = '\0';
272     pwd = getpwnam(user_name);
273   } else {
274     pwd = getpwuid(real_uid);
275   }
276   
277   if (pwd == 0) {
278     fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
279     exit(1);
280   }
281   /* If we are root we don't ask for the old password. */
282   old_passwd[0] = '\0';
283   if (real_uid != 0) {
284     p = getpass("Old SMB password:");
285     strncpy(old_passwd, p, sizeof(fstring));
286     old_passwd[sizeof(fstring)-1] = '\0';
287   }
288   new_passwd[0] = '\0';
289   p = getpass("New SMB password:");
290   strncpy(new_passwd, p, sizeof(fstring));
291   new_passwd[sizeof(fstring)-1] = '\0';
292   p = getpass("Retype new SMB password:");
293   if (strcmp(p, new_passwd)) {
294     fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
295     exit(1);
296   }
297   
298   if (new_passwd[0] == '\0') {
299     printf("Password not set\n");
300     exit(0);
301   }
302   
303   /* Calculate the MD4 hash (NT compatible) of the old and new passwords */
304   memset(old_nt_p16, '\0', 16);
305   E_md4hash((uchar *)old_passwd, old_nt_p16);
306   
307   memset(new_nt_p16, '\0', 16);
308   E_md4hash((uchar *) new_passwd, new_nt_p16);
309   
310   /* Mangle the passwords into Lanman format */
311   old_passwd[14] = '\0';
312   strupper(old_passwd);
313   new_passwd[14] = '\0';
314   strupper(new_passwd);
315   
316   /*
317    * Calculate the SMB (lanman) hash functions of both old and new passwords.
318    */
319   
320   memset(old_p16, '\0', 16);
321   E_P16((uchar *) old_passwd, old_p16);
322   
323   memset(new_p16, '\0', 16);
324   E_P16((uchar *) new_passwd, new_p16);
325   
326   /*
327    * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
328    * filename
329    */
330   if ((fp = fopen(pfile, "r+")) == NULL) {
331     err = errno;
332     fprintf(stderr, "%s: Failed to open password file %s.\n",
333             argv[0], pfile);
334     errno = err;
335     perror(argv[0]);
336     exit(err);
337   }
338   /* Set read buffer to 16k for effiecient reads */
339   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
340   
341   /* make sure it is only rw by the owner */
342   chmod(pfile, 0600);
343   
344   /* Lock the smbpasswd file for write. */
345   if ((lockfd = pw_file_lock(pfile, F_WRLCK, 5)) < 0) {
346     err = errno;
347     fprintf(stderr, "%s: Failed to lock password file %s.\n",
348             argv[0], pfile);
349     fclose(fp);
350     errno = err;
351     perror(argv[0]);
352     exit(err);
353   }
354   /* Get the smb passwd entry for this user */
355   smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd, 
356                                &got_valid_nt_entry, &seekpos);
357   if (smb_pwent == NULL) {
358     if(add_user == False) {
359       fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
360               argv[0], pwd->pw_name, pfile);
361       fclose(fp);
362       pw_file_unlock(lockfd);
363       exit(1);
364     }
365
366     /* Create a new smb passwd entry and set it to the given password. */
367     {
368       int fd;
369       int i;
370       int new_entry_length;
371       char *new_entry;
372       char *p;
373       long offpos;
374
375       /* The add user write needs to be atomic - so get the fd from 
376          the fp and do a raw write() call.
377        */
378       fd = fileno(fp);
379
380       if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
381         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
382 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
383         fclose(fp);
384         pw_file_unlock(lockfd);
385         exit(1);
386       }
387
388       new_entry_length = strlen(pwd->pw_name) + 1 + 15 + 1 + 
389                          32 + 1 + 32 + 1 + strlen(pwd->pw_gecos) + 
390                          1 + strlen(pwd->pw_dir) + 1 + 
391                          strlen(pwd->pw_shell) + 1;
392       if((new_entry = (char *)malloc( new_entry_length )) == 0) {
393         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
394 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
395         fclose(fp);
396         pw_file_unlock(lockfd);
397         exit(1);
398       }
399
400       sprintf(new_entry, "%s:%u:", pwd->pw_name, pwd->pw_uid);
401       p = &new_entry[strlen(new_entry)];
402       for( i = 0; i < 16; i++)
403         sprintf(&p[i*2], "%02X", new_p16[i]);
404       p += 32;
405       *p++ = ':';
406       for( i = 0; i < 16; i++)
407         sprintf(&p[i*2], "%02X", old_p16[i]);
408       p += 32;
409       *p++ = ':';
410       sprintf(p, "%s:%s:%s\n", pwd->pw_gecos, 
411               pwd->pw_dir, pwd->pw_shell);
412       if(write(fd, new_entry, strlen(new_entry)) != strlen(new_entry)) {
413         fprintf(stderr, "%s: Failed to add entry for user %s to file %s. \
414 Error was %d\n", argv[0], pwd->pw_name, pfile, errno);
415         /* Remove the entry we just wrote. */
416         if(ftruncate(fd, offpos) == -1) {
417           fprintf(stderr, "%s: ERROR failed to ftruncate file %s. \
418 Error was %d. Password file may be corrupt ! Please examine by hand !\n", 
419                    argv[0], pwd->pw_name, errno);
420         }
421         fclose(fp);
422         pw_file_unlock(lockfd);
423         exit(1);
424       }
425       
426       fclose(fp);  
427       pw_file_unlock(lockfd);  
428       exit(0);
429     }
430   }
431
432   /* If we are root or the password is 'NO PASSWORD' then
433      we don't need to check the old password. */
434   if (real_uid != 0) {
435     if (valid_old_pwd == False) {
436       fprintf(stderr, "%s: User %s is disabled, plase contact your administrator to enable it.\n", argv[0], pwd->pw_name);
437       fclose(fp);
438       pw_file_unlock(lockfd);
439       exit(1);
440     }
441     /* Check the old Lanman password - NULL means 'NO PASSWORD' */
442     if (smb_pwent->smb_passwd != NULL) {
443       if (memcmp(old_p16, smb_pwent->smb_passwd, 16)) {
444         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
445         fclose(fp);
446         pw_file_unlock(lockfd);
447         exit(1);
448       }
449     }
450     /* Check the NT password if it exists */
451     if (smb_pwent->smb_nt_passwd != NULL) {
452       if (memcmp(old_nt_p16, smb_pwent->smb_nt_passwd, 16)) {
453         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
454         fclose(fp);
455         pw_file_unlock(lockfd);
456         exit(1);
457       }
458     }
459   }
460   /*
461    * If we get here either we were root or the old password checked out
462    * ok.
463    */
464   /* Create the 32 byte representation of the new p16 */
465   for (i = 0; i < 16; i++) {
466     sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
467   }
468   if(got_valid_nt_entry) {
469     /* Add on the NT md4 hash */
470     ascii_p16[32] = ':';
471     for (i = 0; i < 16; i++) {
472       sprintf(&ascii_p16[(i * 2)+33], "%02X", (uchar) new_nt_p16[i]);
473     }
474   }
475   /*
476    * Do an atomic write into the file at the position defined by
477    * seekpos.
478    */
479   pwfd = fileno(fp);
480   ret = lseek(pwfd, seekpos - 1, SEEK_SET);
481   if (ret != seekpos - 1) {
482     err = errno;
483     fprintf(stderr, "%s: seek fail on file %s.\n",
484             argv[0], pfile);
485     fclose(fp);
486     errno = err;
487     perror(argv[0]);
488     pw_file_unlock(lockfd);
489     exit(1);
490   }
491   /* Sanity check - ensure the character is a ':' */
492   if (read(pwfd, &c, 1) != 1) {
493     err = errno;
494     fprintf(stderr, "%s: read fail on file %s.\n",
495             argv[0], pfile);
496     fclose(fp);
497     errno = err;
498     perror(argv[0]);
499     pw_file_unlock(lockfd);
500     exit(1);
501   }
502   if (c != ':') {
503     fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
504             argv[0], pfile);
505     fclose(fp);
506     pw_file_unlock(lockfd);
507     exit(1);
508   }
509   writelen = (got_valid_nt_entry) ? 65 : 32;
510   if (write(pwfd, ascii_p16, writelen) != writelen) {
511     err = errno;
512     fprintf(stderr, "%s: write fail in file %s.\n",
513             argv[0], pfile);
514     fclose(fp);
515     errno = err;
516     perror(argv[0]);
517     pw_file_unlock(lockfd);
518     exit(err);
519   }
520   fclose(fp);
521   pw_file_unlock(lockfd);
522   printf("Password changed\n");
523   return 0;
524 }
525
526 #else
527
528 #include "includes.h"
529
530 int 
531 main(int argc, char **argv)
532 {
533   printf("smb password encryption not selected in Makefile\n");
534   return 0;
535 }
536 #endif