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