Revise our server-side password change code to cope with the various
[tprouty/samba.git] / source / smbd / chgpasswd.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Samba utility functions
4    Copyright (C) Andrew Tridgell 1992-1998
5    Copyright (C) Andrew Bartlett 2001-2004
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 /* These comments regard the code to change the user's unix password: */
23
24 /* fork a child process to exec passwd and write to its
25  * tty to change a users password. This is running as the
26  * user who is attempting to change the password.
27  */
28
29 /* 
30  * This code was copied/borrowed and stolen from various sources.
31  * The primary source was the poppasswd.c from the authors of POPMail. This software
32  * was included as a client to change passwords using the 'passwd' program
33  * on the remote machine.
34  *
35  * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
36  * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
37  * and rights to modify, distribute or incorporate this change to the CAP suite or
38  * using it for any other reason are granted, so long as this disclaimer is left intact.
39  */
40
41 /*
42    This code was hacked considerably for inclusion in Samba, primarily
43    by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
44    of the "password chat" option, which allows the easy runtime
45    specification of the expected sequence of events to change a
46    password.
47    */
48
49 #include "includes.h"
50
51 extern struct passdb_ops pdb_ops;
52
53 static NTSTATUS check_oem_password(const char *user,
54                                    uchar password_encrypted_with_lm_hash[516], 
55                                    const uchar old_lm_hash_encrypted[16],
56                                    uchar password_encrypted_with_nt_hash[516], 
57                                    const uchar old_nt_hash_encrypted[16],
58                                    SAM_ACCOUNT **hnd, char *new_passwd,
59                                    int new_passwd_size);
60
61 #if ALLOW_CHANGE_PASSWORD
62
63 static int findpty(char **slave)
64 {
65         int master;
66         static fstring line;
67         DIR *dirp;
68         const char *dpname;
69
70 #if defined(HAVE_GRANTPT)
71         /* Try to open /dev/ptmx. If that fails, fall through to old method. */
72         if ((master = sys_open("/dev/ptmx", O_RDWR, 0)) >= 0)
73         {
74                 grantpt(master);
75                 unlockpt(master);
76                 *slave = (char *)ptsname(master);
77                 if (*slave == NULL)
78                 {
79                         DEBUG(0,
80                               ("findpty: Unable to create master/slave pty pair.\n"));
81                         /* Stop fd leak on error. */
82                         close(master);
83                         return -1;
84                 }
85                 else
86                 {
87                         DEBUG(10,
88                               ("findpty: Allocated slave pty %s\n", *slave));
89                         return (master);
90                 }
91         }
92 #endif /* HAVE_GRANTPT */
93
94         fstrcpy(line, "/dev/ptyXX");
95
96         dirp = opendir("/dev");
97         if (!dirp)
98                 return (-1);
99         while ((dpname = readdirname(dirp)) != NULL)
100         {
101                 if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5)
102                 {
103                         DEBUG(3,
104                               ("pty: try to open %s, line was %s\n", dpname,
105                                line));
106                         line[8] = dpname[3];
107                         line[9] = dpname[4];
108                         if ((master = sys_open(line, O_RDWR, 0)) >= 0)
109                         {
110                                 DEBUG(3, ("pty: opened %s\n", line));
111                                 line[5] = 't';
112                                 *slave = line;
113                                 closedir(dirp);
114                                 return (master);
115                         }
116                 }
117         }
118         closedir(dirp);
119         return (-1);
120 }
121
122 static int dochild(int master, const char *slavedev, const struct passwd *pass,
123                    const char *passwordprogram, BOOL as_root)
124 {
125         int slave;
126         struct termios stermios;
127         gid_t gid;
128         uid_t uid;
129
130         if (pass == NULL)
131         {
132                 DEBUG(0,
133                       ("dochild: user doesn't exist in the UNIX password database.\n"));
134                 return False;
135         }
136
137         gid = pass->pw_gid;
138         uid = pass->pw_uid;
139
140         gain_root_privilege();
141
142         /* Start new session - gets rid of controlling terminal. */
143         if (setsid() < 0)
144         {
145                 DEBUG(3,
146                       ("Weirdness, couldn't let go of controlling terminal\n"));
147                 return (False);
148         }
149
150         /* Open slave pty and acquire as new controlling terminal. */
151         if ((slave = sys_open(slavedev, O_RDWR, 0)) < 0)
152         {
153                 DEBUG(3, ("More weirdness, could not open %s\n", slavedev));
154                 return (False);
155         }
156 #ifdef I_PUSH
157         ioctl(slave, I_PUSH, "ptem");
158         ioctl(slave, I_PUSH, "ldterm");
159 #elif defined(TIOCSCTTY)
160         if (ioctl(slave, TIOCSCTTY, 0) < 0)
161         {
162                 DEBUG(3, ("Error in ioctl call for slave pty\n"));
163                 /* return(False); */
164         }
165 #endif
166
167         /* Close master. */
168         close(master);
169
170         /* Make slave stdin/out/err of child. */
171
172         if (sys_dup2(slave, STDIN_FILENO) != STDIN_FILENO)
173         {
174                 DEBUG(3, ("Could not re-direct stdin\n"));
175                 return (False);
176         }
177         if (sys_dup2(slave, STDOUT_FILENO) != STDOUT_FILENO)
178         {
179                 DEBUG(3, ("Could not re-direct stdout\n"));
180                 return (False);
181         }
182         if (sys_dup2(slave, STDERR_FILENO) != STDERR_FILENO)
183         {
184                 DEBUG(3, ("Could not re-direct stderr\n"));
185                 return (False);
186         }
187         if (slave > 2)
188                 close(slave);
189
190         /* Set proper terminal attributes - no echo, canonical input processing,
191            no map NL to CR/NL on output. */
192
193         if (tcgetattr(0, &stermios) < 0)
194         {
195                 DEBUG(3,
196                       ("could not read default terminal attributes on pty\n"));
197                 return (False);
198         }
199         stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
200         stermios.c_lflag |= ICANON;
201 #ifdef ONLCR
202         stermios.c_oflag &= ~(ONLCR);
203 #endif
204         if (tcsetattr(0, TCSANOW, &stermios) < 0)
205         {
206                 DEBUG(3, ("could not set attributes of pty\n"));
207                 return (False);
208         }
209
210         /* make us completely into the right uid */
211         if (!as_root)
212         {
213                 become_user_permanently(uid, gid);
214         }
215
216         DEBUG(10,
217               ("Invoking '%s' as password change program.\n",
218                passwordprogram));
219
220         /* execl() password-change application */
221         if (execl("/bin/sh", "sh", "-c", passwordprogram, NULL) < 0)
222         {
223                 DEBUG(3, ("Bad status returned from %s\n", passwordprogram));
224                 return (False);
225         }
226         return (True);
227 }
228
229 static int expect(int master, char *issue, char *expected)
230 {
231         pstring buffer;
232         int attempts, timeout, nread, len;
233         BOOL match = False;
234
235         for (attempts = 0; attempts < 2; attempts++) {
236                 if (!strequal(issue, ".")) {
237                         if (lp_passwd_chat_debug())
238                                 DEBUG(100, ("expect: sending [%s]\n", issue));
239
240                         if ((len = write(master, issue, strlen(issue))) != strlen(issue)) {
241                                 DEBUG(2,("expect: (short) write returned %d\n", len ));
242                                 return False;
243                         }
244                 }
245
246                 if (strequal(expected, "."))
247                         return True;
248
249                 /* Initial timeout. */
250                 timeout = lp_passwd_chat_timeout() * 1000;
251                 nread = 0;
252                 buffer[nread] = 0;
253
254                 while ((len = read_socket_with_timeout(master, buffer + nread, 1,
255                                                        sizeof(buffer) - nread - 1,
256                                                        timeout)) > 0) {
257                         nread += len;
258                         buffer[nread] = 0;
259
260                         {
261                                 /* Eat leading/trailing whitespace before match. */
262                                 pstring str;
263                                 pstrcpy( str, buffer);
264                                 trim_char( str, ' ', ' ');
265
266                                 if ((match = (unix_wild_match(expected, str) == 0))) {
267                                         /* Now data has started to return, lower timeout. */
268                                         timeout = lp_passwd_chat_timeout() * 100;
269                                 }
270                         }
271                 }
272
273                 if (lp_passwd_chat_debug())
274                         DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
275                                     expected, buffer, match ? "yes" : "no" ));
276
277                 if (match)
278                         break;
279
280                 if (len < 0) {
281                         DEBUG(2, ("expect: %s\n", strerror(errno)));
282                         return False;
283                 }
284         }
285
286         DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
287         return match;
288 }
289
290 static void pwd_sub(char *buf)
291 {
292         all_string_sub(buf, "\\n", "\n", 0);
293         all_string_sub(buf, "\\r", "\r", 0);
294         all_string_sub(buf, "\\s", " ", 0);
295         all_string_sub(buf, "\\t", "\t", 0);
296 }
297
298 static int talktochild(int master, const char *seq)
299 {
300         int count = 0;
301         fstring issue, expected;
302
303         fstrcpy(issue, ".");
304
305         while (next_token(&seq, expected, NULL, sizeof(expected)))
306         {
307                 pwd_sub(expected);
308                 count++;
309
310                 if (!expect(master, issue, expected))
311                 {
312                         DEBUG(3, ("Response %d incorrect\n", count));
313                         return False;
314                 }
315
316                 if (!next_token(&seq, issue, NULL, sizeof(issue)))
317                         fstrcpy(issue, ".");
318
319                 pwd_sub(issue);
320         }
321         if (!strequal(issue, ".")) {
322                 /* we have one final issue to send */
323                 fstrcpy(expected, ".");
324                 if (!expect(master, issue, expected))
325                         return False;
326         }
327
328         return (count > 0);
329 }
330
331 static BOOL chat_with_program(char *passwordprogram, struct passwd *pass,
332                               char *chatsequence, BOOL as_root)
333 {
334         char *slavedev;
335         int master;
336         pid_t pid, wpid;
337         int wstat;
338         BOOL chstat = False;
339
340         if (pass == NULL) {
341                 DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
342                 return False;
343         }
344
345         /* allocate a pseudo-terminal device */
346         if ((master = findpty(&slavedev)) < 0) {
347                 DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name));
348                 return (False);
349         }
350
351         /*
352          * We need to temporarily stop CatchChild from eating
353          * SIGCLD signals as it also eats the exit status code. JRA.
354          */
355
356         CatchChildLeaveStatus();
357
358         if ((pid = sys_fork()) < 0) {
359                 DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name));
360                 close(master);
361                 CatchChild();
362                 return (False);
363         }
364
365         /* we now have a pty */
366         if (pid > 0) {                  /* This is the parent process */
367                 if ((chstat = talktochild(master, chatsequence)) == False) {
368                         DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name));
369                         kill(pid, SIGKILL);     /* be sure to end this process */
370                 }
371
372                 while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
373                         if (errno == EINTR) {
374                                 errno = 0;
375                                 continue;
376                         }
377                         break;
378                 }
379
380                 if (wpid < 0) {
381                         DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n"));
382                         close(master);
383                         CatchChild();
384                         return (False);
385                 }
386
387                 /*
388                  * Go back to ignoring children.
389                  */
390                 CatchChild();
391
392                 close(master);
393
394                 if (pid != wpid) {
395                         DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n"));
396                         return (False);
397                 }
398                 if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) {
399                         DEBUG(3, ("chat_with_program: The process exited with status %d \
400 while we were waiting\n", WEXITSTATUS(wstat)));
401                         return (False);
402                 }
403 #if defined(WIFSIGNALLED) && defined(WTERMSIG)
404                 else if (WIFSIGNALLED(wstat)) {
405                         DEBUG(3, ("chat_with_program: The process was killed by signal %d \
406 while we were waiting\n", WTERMSIG(wstat)));
407                         return (False);
408                 }
409 #endif
410         } else {
411                 /* CHILD */
412
413                 /*
414                  * Lose any oplock capabilities.
415                  */
416                 oplock_set_capability(False, False);
417
418                 /* make sure it doesn't freeze */
419                 alarm(20);
420
421                 if (as_root)
422                         become_root();
423
424                 DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name,
425                        (int)getuid(), (int)getgid(), BOOLSTR(as_root) ));
426                 chstat = dochild(master, slavedev, pass, passwordprogram, as_root);
427
428                 if (as_root)
429                         unbecome_root();
430
431                 /*
432                  * The child should never return from dochild() ....
433                  */
434
435                 DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat));
436                 exit(1);
437         }
438
439         if (chstat)
440                 DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n",
441                        (chstat ? "" : "un"), pass->pw_name));
442         return (chstat);
443 }
444
445 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
446 {
447         pstring passwordprogram;
448         pstring chatsequence;
449         size_t i;
450         size_t len;
451
452         struct passwd *pass;
453
454         if (!name) {
455                 DEBUG(1, ("chgpasswd: NULL username specfied !\n"));
456         }
457         
458         pass = Get_Pwnam(name);
459         if (!pass) {
460                 DEBUG(1, ("chgpasswd: Username does not exist in system !\n"));
461                 return False;
462         }
463
464         if (!oldpass) {
465                 oldpass = "";
466         }
467
468         DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name));
469
470 #if DEBUG_PASSWORD
471         DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass));
472 #endif
473
474         /* Take the passed information and test it for minimum criteria */
475         /* Minimum password length */
476         if (strlen(newpass) < lp_min_passwd_length()) {
477                 /* too short, must be at least MINPASSWDLENGTH */
478                 DEBUG(0, ("chgpasswd: Password Change: user %s, New password is shorter than minimum password length = %d\n",
479                        name, lp_min_passwd_length()));
480                 return (False); /* inform the user */
481         }
482
483         /* Password is same as old password */
484         if (strcmp(oldpass, newpass) == 0) {
485                 /* don't allow same password */
486                 DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name));      /* log the attempt */
487                 return (False); /* inform the user */
488         }
489
490         /* 
491          * Check the old and new passwords don't contain any control
492          * characters.
493          */
494
495         len = strlen(oldpass);
496         for (i = 0; i < len; i++) {
497                 if (iscntrl((int)oldpass[i])) {
498                         DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n"));
499                         return False;
500                 }
501         }
502
503         len = strlen(newpass);
504         for (i = 0; i < len; i++) {
505                 if (iscntrl((int)newpass[i])) {
506                         DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n"));
507                         return False;
508                 }
509         }
510         
511 #ifdef WITH_PAM
512         if (lp_pam_password_change()) {
513                 BOOL ret;
514
515                 if (as_root)
516                         become_root();
517
518                 if (pass) {
519                         ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
520                 } else {
521                         ret = smb_pam_passchange(name, oldpass, newpass);
522                 }
523                         
524                 if (as_root)
525                         unbecome_root();
526
527                 return ret;
528         }
529 #endif
530
531         /* A non-PAM password change just doen't make sense without a valid local user */
532
533         if (pass == NULL) {
534                 DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name));
535                 return False;
536         }
537
538         pstrcpy(passwordprogram, lp_passwd_program());
539         pstrcpy(chatsequence, lp_passwd_chat());
540
541         if (!*chatsequence) {
542                 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
543                 return (False);
544         }
545
546         if (!*passwordprogram) {
547                 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
548                 return (False);
549         }
550
551         if (as_root) {
552                 /* The password program *must* contain the user name to work. Fail if not. */
553                 if (strstr(passwordprogram, "%u") == NULL) {
554                         DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
555 the string %%u, and the given string %s does not.\n", passwordprogram ));
556                         return False;
557                 }
558         }
559
560         pstring_sub(passwordprogram, "%u", name);
561         /* note that we do NOT substitute the %o and %n in the password program
562            as this would open up a security hole where the user could use
563            a new password containing shell escape characters */
564
565         pstring_sub(chatsequence, "%u", name);
566         all_string_sub(chatsequence, "%o", oldpass, sizeof(pstring));
567         all_string_sub(chatsequence, "%n", newpass, sizeof(pstring));
568         return (chat_with_program
569                 (passwordprogram, pass, chatsequence, as_root));
570 }
571
572 #else /* ALLOW_CHANGE_PASSWORD */
573
574 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
575 {
576         DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name));
577         return (False);
578 }
579 #endif /* ALLOW_CHANGE_PASSWORD */
580
581 /***********************************************************
582  Code to check the lanman hashed password.
583 ************************************************************/
584
585 BOOL check_lanman_password(char *user, uchar * pass1,
586                            uchar * pass2, SAM_ACCOUNT **hnd)
587 {
588         uchar unenc_new_pw[16];
589         uchar unenc_old_pw[16];
590         SAM_ACCOUNT *sampass = NULL;
591         uint16 acct_ctrl;
592         const uint8 *lanman_pw;
593         BOOL ret;
594         
595         become_root();
596         ret = pdb_getsampwnam(sampass, user);
597         unbecome_root();
598
599         if (ret == False) {
600                 DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n"));
601                 pdb_free_sam(&sampass);
602                 return False;
603         }
604         
605         acct_ctrl = pdb_get_acct_ctrl     (sampass);
606         lanman_pw = pdb_get_lanman_passwd (sampass);
607
608         if (acct_ctrl & ACB_DISABLED) {
609                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
610                 pdb_free_sam(&sampass);
611                 return False;
612         }
613
614         if (lanman_pw == NULL) {
615                 if (acct_ctrl & ACB_PWNOTREQ) {
616                         /* this saves the pointer for the caller */
617                         *hnd = sampass;
618                         return True;
619                 } else {
620                         DEBUG(0, ("check_lanman_password: no lanman password !\n"));
621                         pdb_free_sam(&sampass);
622                         return False;
623                 }
624         }
625
626         /* Get the new lanman hash. */
627         D_P16(lanman_pw, pass2, unenc_new_pw);
628
629         /* Use this to get the old lanman hash. */
630         D_P16(unenc_new_pw, pass1, unenc_old_pw);
631
632         /* Check that the two old passwords match. */
633         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
634                 DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
635                 pdb_free_sam(&sampass);
636                 return False;
637         }
638
639         /* this saves the pointer for the caller */
640         *hnd = sampass;
641         return True;
642 }
643
644 /***********************************************************
645  Code to change the lanman hashed password.
646  It nulls out the NT hashed password as it will
647  no longer be valid.
648  NOTE this function is designed to be called as root. Check the old password
649  is correct before calling. JRA.
650 ************************************************************/
651
652 BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2)
653 {
654         static uchar null_pw[16];
655         uchar unenc_new_pw[16];
656         BOOL ret;
657         uint16 acct_ctrl;
658         const uint8 *pwd;
659
660         if (sampass == NULL) {
661                 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
662                 return False;
663         }
664         
665         acct_ctrl = pdb_get_acct_ctrl(sampass);
666         pwd = pdb_get_lanman_passwd(sampass);
667
668         if (acct_ctrl & ACB_DISABLED) {
669                 DEBUG(0,("change_lanman_password: account %s disabled.\n",
670                        pdb_get_username(sampass)));
671                 return False;
672         }
673
674         if (pwd == NULL) { 
675                 if (acct_ctrl & ACB_PWNOTREQ) {
676                         uchar no_pw[14];
677                         memset(no_pw, '\0', 14);
678                         E_P16(no_pw, null_pw);
679
680                         /* Get the new lanman hash. */
681                         D_P16(null_pw, pass2, unenc_new_pw);
682                 } else {
683                         DEBUG(0,("change_lanman_password: no lanman password !\n"));
684                         return False;
685                 }
686         } else {
687                 /* Get the new lanman hash. */
688                 D_P16(pwd, pass2, unenc_new_pw);
689         }
690
691         if (!pdb_set_lanman_passwd(sampass, unenc_new_pw, PDB_CHANGED)) {
692                 return False;
693         }
694
695         if (!pdb_set_nt_passwd    (sampass, NULL, PDB_CHANGED)) {
696                 return False;   /* We lose the NT hash. Sorry. */
697         }
698
699         if (!pdb_set_pass_changed_now  (sampass)) {
700                 pdb_free_sam(&sampass);
701                 /* Not quite sure what this one qualifies as, but this will do */
702                 return False; 
703         }
704  
705         /* Now flush the sam_passwd struct to persistent storage */
706         ret = pdb_update_sam_account (sampass);
707
708         return ret;
709 }
710
711 /***********************************************************
712  Code to check and change the OEM hashed password.
713 ************************************************************/
714
715 NTSTATUS pass_oem_change(char *user,
716                          uchar password_encrypted_with_lm_hash[516], 
717                          const uchar old_lm_hash_encrypted[16],
718                          uchar password_encrypted_with_nt_hash[516], 
719                          const uchar old_nt_hash_encrypted[16])
720 {
721         pstring new_passwd;
722         SAM_ACCOUNT *sampass = NULL;
723         NTSTATUS nt_status = check_oem_password(user, password_encrypted_with_lm_hash, 
724                                                 old_lm_hash_encrypted, 
725                                                 password_encrypted_with_nt_hash, 
726                                                 old_nt_hash_encrypted,
727                                                 &sampass, new_passwd, sizeof(new_passwd));
728         
729         if (!NT_STATUS_IS_OK(nt_status))
730                 return nt_status;
731
732         /* We've already checked the old password here.... */
733         become_root();
734         nt_status = change_oem_password(sampass, NULL, new_passwd, True);
735         unbecome_root();
736
737         memset(new_passwd, 0, sizeof(new_passwd));
738
739         pdb_free_sam(&sampass);
740
741         return nt_status;
742 }
743
744 /***********************************************************
745  Decrypt and verify a user password change.  
746
747  The 516 byte long buffers are encrypted with the old NT and 
748  old LM passwords, and if the NT passwords are present, both 
749  buffers contain a unicode string.
750
751  After decrypting the buffers, check the password is correct by
752  matching the old hashed passwords with the passwords in the passdb.
753  
754 ************************************************************/
755
756 static NTSTATUS check_oem_password(const char *user,
757                                    uchar password_encrypted_with_lm_hash[516], 
758                                    const uchar old_lm_hash_encrypted[16],
759                                    uchar password_encrypted_with_nt_hash[516], 
760                                    const uchar old_nt_hash_encrypted[16],
761                                    SAM_ACCOUNT **hnd, char *new_passwd,
762                                    int new_passwd_size)
763 {
764         static uchar null_pw[16];
765         static uchar null_ntpw[16];
766         SAM_ACCOUNT *sampass = NULL;
767         char *password_encrypted;
768         const char *encryption_key;
769         const uint8 *lanman_pw, *nt_pw;
770         uint16 acct_ctrl;
771         uint32 new_pw_len;
772         uchar new_nt_hash[16];
773         uchar old_nt_hash_plain[16];
774         uchar new_lm_hash[16];
775         uchar old_lm_hash_plain[16];
776         char no_pw[2];
777         BOOL ret;
778
779         BOOL nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted);
780         BOOL lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted);
781
782         *hnd = NULL;
783
784         pdb_init_sam(&sampass);
785
786         become_root();
787         ret = pdb_getsampwnam(sampass, user);
788         unbecome_root();
789
790         if (ret == False) {
791                 DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
792                 pdb_free_sam(&sampass);
793                 return NT_STATUS_NO_SUCH_USER; 
794         }
795
796         acct_ctrl = pdb_get_acct_ctrl(sampass);
797         
798         if (acct_ctrl & ACB_DISABLED) {
799                 DEBUG(2,("check_lanman_password: account %s disabled.\n", user));
800                 pdb_free_sam(&sampass);
801                 return NT_STATUS_ACCOUNT_DISABLED;
802         }
803
804         if (acct_ctrl & ACB_PWNOTREQ && lp_null_passwords()) {
805                 /* construct a null password (in case one is needed */
806                 no_pw[0] = 0;
807                 no_pw[1] = 0;
808                 nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
809                 lanman_pw = null_pw;
810                 nt_pw = null_pw;
811
812         } else {
813                 /* save pointers to passwords so we don't have to keep looking them up */
814                 if (lp_lanman_auth()) {
815                         lanman_pw = pdb_get_lanman_passwd(sampass);
816                 } else {
817                         lanman_pw = NULL;
818                 }
819                 nt_pw = pdb_get_nt_passwd(sampass);
820         }
821
822         if (nt_pw && nt_pass_set) {
823                 /* IDEAL Case: passwords are in unicode, and we can
824                  * read use the password encrypted with the NT hash 
825                  */
826                 password_encrypted = password_encrypted_with_nt_hash;
827                 encryption_key = nt_pw;
828         } else if (lanman_pw && lm_pass_set) {
829                 /* password may still be in unicode, but use LM hash version */
830                 password_encrypted = password_encrypted_with_lm_hash;
831                 encryption_key = lanman_pw;
832         } else if (nt_pass_set) {
833                 DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n", 
834                           user));
835                 pdb_free_sam(&sampass);
836                 return NT_STATUS_WRONG_PASSWORD;        
837         } else if (lm_pass_set) {
838                 DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n", 
839                           user));
840                 pdb_free_sam(&sampass);
841                 return NT_STATUS_WRONG_PASSWORD;
842         } else {
843                 DEBUG(1, ("password change requested for user %s, but no password supplied!\n", 
844                           user));
845                 pdb_free_sam(&sampass);
846                 return NT_STATUS_WRONG_PASSWORD;
847         }
848
849         /* 
850          * Decrypt the password with the key 
851          */
852         SamOEMhash( password_encrypted, encryption_key, 516);
853
854         if ( !decode_pw_buffer(password_encrypted, new_passwd, new_passwd_size, &new_pw_len, 
855                                nt_pass_set ? STR_UNICODE : STR_ASCII)) {
856                 pdb_free_sam(&sampass);
857                 return NT_STATUS_WRONG_PASSWORD;
858         }
859
860         /*
861          * To ensure we got the correct new password, hash it and
862          * use it as a key to test the passed old password.
863          */
864
865         if (nt_pass_set) {
866                 /* NT passwords, verify the NT hash. */
867                 
868                 /* Calculate the MD4 hash (NT compatible) of the password */
869                 memset(new_nt_hash, '\0', 16);
870                 E_md4hash(new_passwd, new_nt_hash);
871
872                 if (nt_pw) {
873                         /*
874                          * Now use new_nt_hash as the key to see if the old
875                          * password matches.
876                          */
877                         D_P16(new_nt_hash, old_nt_hash_encrypted, old_nt_hash_plain);
878                         
879                         if (memcmp(nt_pw, old_nt_hash_plain, 16)) {
880                                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
881                                 pdb_free_sam(&sampass);
882                                 return NT_STATUS_WRONG_PASSWORD;
883                         }
884                         
885                         /* We could check the LM password here, but there is
886                          * little point, we already know the password is
887                          * correct, and the LM password might not even be
888                          * present. */
889
890                         /* Further, LM hash generation algorithms
891                          * differ with charset, so we could
892                          * incorrectly fail a perfectly valid password
893                          * change */
894 #ifdef DEBUG_PASSWORD
895                         DEBUG(100,
896                               ("check_oem_password: password %s ok\n", new_passwd));
897 #endif
898                         *hnd = sampass;
899                         return NT_STATUS_OK;
900                 }
901                 
902                 if (lanman_pw) {
903                         /*
904                          * Now use new_nt_hash as the key to see if the old
905                          * LM password matches.
906                          */
907                         D_P16(new_nt_hash, old_lm_hash_encrypted, old_lm_hash_plain);
908                         
909                         if (memcmp(lanman_pw, old_lm_hash_plain, 16)) {
910                                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
911                                 pdb_free_sam(&sampass);
912                                 return NT_STATUS_WRONG_PASSWORD;
913                         }
914 #ifdef DEBUG_PASSWORD
915                         DEBUG(100,
916                               ("check_oem_password: password %s ok\n", new_passwd));
917 #endif
918                         *hnd = sampass;
919                         return NT_STATUS_OK;
920                 }
921         }
922
923         if (lanman_pw && lm_pass_set) {
924
925                 E_deshash(new_passwd, new_lm_hash);
926
927                 /*
928                  * Now use new_lm_hash as the key to see if the old
929                  * password matches.
930                  */
931                 D_P16(new_lm_hash, old_lm_hash_encrypted, old_lm_hash_plain);
932                 
933                 if (memcmp(lanman_pw, old_lm_hash_plain, 16)) {
934                         DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
935                         pdb_free_sam(&sampass);
936                         return NT_STATUS_WRONG_PASSWORD;
937                 }
938                 
939 #ifdef DEBUG_PASSWORD
940                 DEBUG(100,
941                       ("check_oem_password: password %s ok\n", new_passwd));
942 #endif
943                 *hnd = sampass;
944                 return NT_STATUS_OK;
945         }
946
947         /* should not be reached */
948         pdb_free_sam(&sampass);
949         return NT_STATUS_WRONG_PASSWORD;
950 }
951
952 /***********************************************************
953  Code to change the oem password. Changes both the lanman
954  and NT hashes.  Old_passwd is almost always NULL.
955  NOTE this function is designed to be called as root. Check the old password
956  is correct before calling. JRA.
957 ************************************************************/
958
959 NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd, BOOL as_root)
960 {
961         BOOL ret;
962         uint32 min_len;
963
964         if (time(NULL) < pdb_get_pass_can_change_time(hnd)) {
965                 DEBUG(1, ("user %s cannot change password now, must wait until %s\n", 
966                           pdb_get_username(hnd), http_timestring(pdb_get_pass_can_change_time(hnd))));
967                 return NT_STATUS_PASSWORD_RESTRICTION;
968         }
969
970         if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen(new_passwd) < min_len)) {
971                 DEBUG(1, ("user %s cannot change password - password too short\n", 
972                           pdb_get_username(hnd)));
973                 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
974                 return NT_STATUS_PASSWORD_RESTRICTION;
975 /*              return NT_STATUS_PWD_TOO_SHORT; */
976         }
977
978         /* Take the passed information and test it for minimum criteria */
979         /* Minimum password length */
980         if (strlen(new_passwd) < lp_min_passwd_length()) {
981                 /* too short, must be at least MINPASSWDLENGTH */
982                 DEBUG(1, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
983                        pdb_get_username(hnd), lp_min_passwd_length()));
984                 return NT_STATUS_PASSWORD_RESTRICTION;
985 /*              return NT_STATUS_PWD_TOO_SHORT; */
986         }
987
988         /* TODO:  Add cracklib support here */
989
990         /*
991          * If unix password sync was requested, attempt to change
992          * the /etc/passwd database first. Return failure if this cannot
993          * be done.
994          *
995          * This occurs before the oem change, because we don't want to
996          * update it if chgpasswd failed.
997          *
998          * Conditional on lp_unix_password_sync() because we don't want
999          * to touch the unix db unless we have admin permission.
1000          */
1001         
1002         if(lp_unix_password_sync() &&
1003                 !chgpasswd(pdb_get_username(hnd), old_passwd, new_passwd, as_root)) {
1004                 return NT_STATUS_ACCESS_DENIED;
1005         }
1006
1007         if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
1008                 return NT_STATUS_ACCESS_DENIED;
1009         }
1010
1011         /* Now write it into the file. */
1012         ret = pdb_update_sam_account (hnd);
1013
1014         if (!ret) {
1015                 return NT_STATUS_ACCESS_DENIED;
1016         }
1017         
1018         return NT_STATUS_OK;
1019 }