2b3a48a75bf123c5fa48d9239740ab9781f66549
[samba.git] / source / 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.
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 void 
205 usage(char *name)
206 {
207         fprintf(stderr, "Usage is : %s [username]\n", name);
208         exit(1);
209 }
210
211 int main(int argc, char **argv)
212 {
213   int             real_uid;
214   struct passwd  *pwd;
215   fstring         old_passwd;
216   uchar           old_p16[16];
217   uchar           old_nt_p16[16];
218   fstring         new_passwd;
219   uchar           new_p16[16];
220   uchar           new_nt_p16[16];
221   char           *p;
222   struct smb_passwd *smb_pwent;
223   FILE           *fp;
224   BOOL            valid_old_pwd = False;
225   BOOL                  got_valid_nt_entry = 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   setup_logging(argv[0],True);
236   
237   charset_initialise();
238   
239 #ifndef DEBUG_PASSWORD
240   /* Check the effective uid */
241   if (geteuid() != 0) {
242     fprintf(stderr, "%s: Must be setuid root.\n", argv[0]);
243     exit(1);
244   }
245 #endif
246   
247   /* Get the real uid */
248   real_uid = getuid();
249   
250   /* Deal with usage problems */
251   if (real_uid == 0) {
252     /* As root we can change anothers password. */
253     if (argc != 1 && argc != 2)
254       usage(argv[0]);
255   } else if (argc != 1)
256     usage(argv[0]);
257   
258   
259   if (real_uid == 0 && argc == 2) {
260     /* If we are root we can change anothers password. */
261     strncpy(user_name, argv[1], sizeof(user_name) - 1);
262     user_name[sizeof(user_name) - 1] = '\0';
263     pwd = getpwnam(user_name);
264   } else {
265     pwd = getpwuid(real_uid);
266   }
267   
268   if (pwd == 0) {
269     fprintf(stderr, "%s: Unable to get UNIX password entry for user.\n", argv[0]);
270     exit(1);
271   }
272   /* If we are root we don't ask for the old password. */
273   old_passwd[0] = '\0';
274   if (real_uid != 0) {
275     p = getpass("Old SMB password:");
276     strncpy(old_passwd, p, sizeof(fstring));
277     old_passwd[sizeof(fstring)-1] = '\0';
278   }
279   new_passwd[0] = '\0';
280   p = getpass("New SMB password:");
281   strncpy(new_passwd, p, sizeof(fstring));
282   new_passwd[sizeof(fstring)-1] = '\0';
283   p = getpass("Retype new SMB password:");
284   if (strcmp(p, new_passwd)) {
285     fprintf(stderr, "%s: Mismatch - password unchanged.\n", argv[0]);
286     exit(1);
287   }
288   
289   if (new_passwd[0] == '\0') {
290     printf("Password not set\n");
291     exit(0);
292   }
293   
294   /* Calculate the MD4 hash (NT compatible) of the old and new passwords */
295   memset(old_nt_p16, '\0', 16);
296   E_md4hash((uchar *)old_passwd, old_nt_p16);
297   
298   memset(new_nt_p16, '\0', 16);
299   E_md4hash((uchar *) new_passwd, new_nt_p16);
300   
301   /* Mangle the passwords into Lanman format */
302   old_passwd[14] = '\0';
303   strupper(old_passwd);
304   new_passwd[14] = '\0';
305   strupper(new_passwd);
306   
307   /*
308    * Calculate the SMB (lanman) hash functions of both old and new passwords.
309    */
310   
311   memset(old_p16, '\0', 16);
312   E_P16((uchar *) old_passwd, old_p16);
313   
314   memset(new_p16, '\0', 16);
315   E_P16((uchar *) new_passwd, new_p16);
316   
317   /*
318    * Open the smbpaswd file XXXX - we need to parse smb.conf to get the
319    * filename
320    */
321   if ((fp = fopen(pfile, "r+")) == NULL) {
322     err = errno;
323     fprintf(stderr, "%s: Failed to open password file %s.\n",
324             argv[0], pfile);
325     errno = err;
326     perror(argv[0]);
327     exit(err);
328   }
329   /* Set read buffer to 16k for effiecient reads */
330   setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
331   
332   /* make sure it is only rw by the owner */
333   chmod(pfile, 0600);
334   
335   /* Lock the smbpasswd file for write. */
336   if ((lockfd = pw_file_lock(pfile, F_WRLCK, 5)) < 0) {
337     err = errno;
338     fprintf(stderr, "%s: Failed to lock password file %s.\n",
339             argv[0], pfile);
340     fclose(fp);
341     errno = err;
342     perror(argv[0]);
343     exit(err);
344   }
345   /* Get the smb passwd entry for this user */
346   smb_pwent = _my_get_smbpwnam(fp, pwd->pw_name, &valid_old_pwd, 
347                                &got_valid_nt_entry, &seekpos);
348   if (smb_pwent == NULL) {
349     fprintf(stderr, "%s: Failed to find entry for user %s in file %s.\n",
350             argv[0], pwd->pw_name, pfile);
351     fclose(fp);
352     pw_file_unlock(lockfd);
353     exit(1);
354   }
355   /* If we are root we don't need to check the old password. */
356   if (real_uid != 0) {
357     if ((valid_old_pwd == False) || (smb_pwent->smb_passwd == NULL)) {
358       fprintf(stderr, "%s: User %s is disabled, plase contact your administrator to enable it.\n", argv[0], pwd->pw_name);
359       fclose(fp);
360       pw_file_unlock(lockfd);
361       exit(1);
362     }
363     /* Check the old Lanman password */
364     if (memcmp(old_p16, smb_pwent->smb_passwd, 16)) {
365       fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
366       fclose(fp);
367       pw_file_unlock(lockfd);
368       exit(1);
369     }
370     /* Check the NT password if it exists */
371     if (smb_pwent->smb_nt_passwd != NULL) {
372       if (memcmp(old_nt_p16, smb_pwent->smb_nt_passwd, 16)) {
373         fprintf(stderr, "%s: Couldn't change password.\n", argv[0]);
374         fclose(fp);
375         pw_file_unlock(lockfd);
376         exit(1);
377       }
378     }
379   }
380   /*
381    * If we get here either we were root or the old password checked out
382    * ok.
383    */
384   /* Create the 32 byte representation of the new p16 */
385   for (i = 0; i < 16; i++) {
386     sprintf(&ascii_p16[i * 2], "%02X", (uchar) new_p16[i]);
387   }
388   if(got_valid_nt_entry) {
389     /* Add on the NT md4 hash */
390     ascii_p16[32] = ':';
391     for (i = 0; i < 16; i++) {
392       sprintf(&ascii_p16[(i * 2)+33], "%02X", (uchar) new_nt_p16[i]);
393     }
394   }
395   /*
396    * Do an atomic write into the file at the position defined by
397    * seekpos.
398    */
399   pwfd = fileno(fp);
400   ret = lseek(pwfd, seekpos - 1, SEEK_SET);
401   if (ret != seekpos - 1) {
402     err = errno;
403     fprintf(stderr, "%s: seek fail on file %s.\n",
404             argv[0], pfile);
405     fclose(fp);
406     errno = err;
407     perror(argv[0]);
408     pw_file_unlock(lockfd);
409     exit(1);
410   }
411   /* Sanity check - ensure the character is a ':' */
412   if (read(pwfd, &c, 1) != 1) {
413     err = errno;
414     fprintf(stderr, "%s: read fail on file %s.\n",
415             argv[0], pfile);
416     fclose(fp);
417     errno = err;
418     perror(argv[0]);
419     pw_file_unlock(lockfd);
420     exit(1);
421   }
422   if (c != ':') {
423     fprintf(stderr, "%s: sanity check on passwd file %s failed.\n",
424             argv[0], pfile);
425     fclose(fp);
426     pw_file_unlock(lockfd);
427     exit(1);
428   }
429   writelen = (got_valid_nt_entry) ? 65 : 32;
430   if (write(pwfd, ascii_p16, writelen) != writelen) {
431     err = errno;
432     fprintf(stderr, "%s: write fail in file %s.\n",
433             argv[0], pfile);
434     fclose(fp);
435     errno = err;
436     perror(argv[0]);
437     pw_file_unlock(lockfd);
438     exit(err);
439   }
440   fclose(fp);
441   pw_file_unlock(lockfd);
442   printf("Password changed\n");
443   return 0;
444 }
445
446 #else
447
448 #include "includes.h"
449
450 int 
451 main(int argc, char **argv)
452 {
453   printf("smb password encryption not selected in Makefile\n");
454   return 0;
455 }
456 #endif