r2331: check password script code and example from trunk
[sfrench/samba-autobuild/.git] / source3 / 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, const 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 struct passwd *pass, 
446                const char *oldpass, const char *newpass, BOOL as_root)
447 {
448         pstring passwordprogram;
449         pstring chatsequence;
450         size_t i;
451         size_t len;
452
453         if (!oldpass) {
454                 oldpass = "";
455         }
456
457         DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name));
458
459 #if DEBUG_PASSWORD
460         DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass));
461 #endif
462
463         /* Take the passed information and test it for minimum criteria */
464
465         /* Password is same as old password */
466         if (strcmp(oldpass, newpass) == 0) {
467                 /* don't allow same password */
468                 DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name));      /* log the attempt */
469                 return (False); /* inform the user */
470         }
471
472         /* 
473          * Check the old and new passwords don't contain any control
474          * characters.
475          */
476
477         len = strlen(oldpass);
478         for (i = 0; i < len; i++) {
479                 if (iscntrl((int)oldpass[i])) {
480                         DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n"));
481                         return False;
482                 }
483         }
484
485         len = strlen(newpass);
486         for (i = 0; i < len; i++) {
487                 if (iscntrl((int)newpass[i])) {
488                         DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n"));
489                         return False;
490                 }
491         }
492         
493 #ifdef WITH_PAM
494         if (lp_pam_password_change()) {
495                 BOOL ret;
496
497                 if (as_root)
498                         become_root();
499
500                 if (pass) {
501                         ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
502                 } else {
503                         ret = smb_pam_passchange(name, oldpass, newpass);
504                 }
505                         
506                 if (as_root)
507                         unbecome_root();
508
509                 return ret;
510         }
511 #endif
512
513         /* A non-PAM password change just doen't make sense without a valid local user */
514
515         if (pass == NULL) {
516                 DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name));
517                 return False;
518         }
519
520         pstrcpy(passwordprogram, lp_passwd_program());
521         pstrcpy(chatsequence, lp_passwd_chat());
522
523         if (!*chatsequence) {
524                 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
525                 return (False);
526         }
527
528         if (!*passwordprogram) {
529                 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
530                 return (False);
531         }
532
533         if (as_root) {
534                 /* The password program *must* contain the user name to work. Fail if not. */
535                 if (strstr_m(passwordprogram, "%u") == NULL) {
536                         DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
537 the string %%u, and the given string %s does not.\n", passwordprogram ));
538                         return False;
539                 }
540         }
541
542         pstring_sub(passwordprogram, "%u", name);
543         /* note that we do NOT substitute the %o and %n in the password program
544            as this would open up a security hole where the user could use
545            a new password containing shell escape characters */
546
547         pstring_sub(chatsequence, "%u", name);
548         all_string_sub(chatsequence, "%o", oldpass, sizeof(pstring));
549         all_string_sub(chatsequence, "%n", newpass, sizeof(pstring));
550         return (chat_with_program
551                 (passwordprogram, pass, chatsequence, as_root));
552 }
553
554 #else /* ALLOW_CHANGE_PASSWORD */
555
556 BOOL chgpasswd(const char *name, const struct passwd *pass, 
557                const char *oldpass, const char *newpass, BOOL as_root)
558 {
559         DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name));
560         return (False);
561 }
562 #endif /* ALLOW_CHANGE_PASSWORD */
563
564 /***********************************************************
565  Code to check the lanman hashed password.
566 ************************************************************/
567
568 BOOL check_lanman_password(char *user, uchar * pass1,
569                            uchar * pass2, SAM_ACCOUNT **hnd)
570 {
571         uchar unenc_new_pw[16];
572         uchar unenc_old_pw[16];
573         SAM_ACCOUNT *sampass = NULL;
574         uint16 acct_ctrl;
575         const uint8 *lanman_pw;
576         BOOL ret;
577         
578         become_root();
579         ret = pdb_getsampwnam(sampass, user);
580         unbecome_root();
581
582         if (ret == False) {
583                 DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n"));
584                 pdb_free_sam(&sampass);
585                 return False;
586         }
587         
588         acct_ctrl = pdb_get_acct_ctrl     (sampass);
589         lanman_pw = pdb_get_lanman_passwd (sampass);
590
591         if (acct_ctrl & ACB_DISABLED) {
592                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
593                 pdb_free_sam(&sampass);
594                 return False;
595         }
596
597         if (lanman_pw == NULL) {
598                 if (acct_ctrl & ACB_PWNOTREQ) {
599                         /* this saves the pointer for the caller */
600                         *hnd = sampass;
601                         return True;
602                 } else {
603                         DEBUG(0, ("check_lanman_password: no lanman password !\n"));
604                         pdb_free_sam(&sampass);
605                         return False;
606                 }
607         }
608
609         /* Get the new lanman hash. */
610         D_P16(lanman_pw, pass2, unenc_new_pw);
611
612         /* Use this to get the old lanman hash. */
613         D_P16(unenc_new_pw, pass1, unenc_old_pw);
614
615         /* Check that the two old passwords match. */
616         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
617                 DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
618                 pdb_free_sam(&sampass);
619                 return False;
620         }
621
622         /* this saves the pointer for the caller */
623         *hnd = sampass;
624         return True;
625 }
626
627 /***********************************************************
628  Code to change the lanman hashed password.
629  It nulls out the NT hashed password as it will
630  no longer be valid.
631  NOTE this function is designed to be called as root. Check the old password
632  is correct before calling. JRA.
633 ************************************************************/
634
635 BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2)
636 {
637         static uchar null_pw[16];
638         uchar unenc_new_pw[16];
639         BOOL ret;
640         uint16 acct_ctrl;
641         const uint8 *pwd;
642
643         if (sampass == NULL) {
644                 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
645                 return False;
646         }
647         
648         acct_ctrl = pdb_get_acct_ctrl(sampass);
649         pwd = pdb_get_lanman_passwd(sampass);
650
651         if (acct_ctrl & ACB_DISABLED) {
652                 DEBUG(0,("change_lanman_password: account %s disabled.\n",
653                        pdb_get_username(sampass)));
654                 return False;
655         }
656
657         if (pwd == NULL) { 
658                 if (acct_ctrl & ACB_PWNOTREQ) {
659                         uchar no_pw[14];
660                         memset(no_pw, '\0', 14);
661                         E_P16(no_pw, null_pw);
662
663                         /* Get the new lanman hash. */
664                         D_P16(null_pw, pass2, unenc_new_pw);
665                 } else {
666                         DEBUG(0,("change_lanman_password: no lanman password !\n"));
667                         return False;
668                 }
669         } else {
670                 /* Get the new lanman hash. */
671                 D_P16(pwd, pass2, unenc_new_pw);
672         }
673
674         if (!pdb_set_lanman_passwd(sampass, unenc_new_pw, PDB_CHANGED)) {
675                 return False;
676         }
677
678         if (!pdb_set_nt_passwd    (sampass, NULL, PDB_CHANGED)) {
679                 return False;   /* We lose the NT hash. Sorry. */
680         }
681
682         if (!pdb_set_pass_changed_now  (sampass)) {
683                 pdb_free_sam(&sampass);
684                 /* Not quite sure what this one qualifies as, but this will do */
685                 return False; 
686         }
687  
688         /* Now flush the sam_passwd struct to persistent storage */
689         ret = pdb_update_sam_account (sampass);
690
691         return ret;
692 }
693
694 /***********************************************************
695  Code to check and change the OEM hashed password.
696 ************************************************************/
697
698 NTSTATUS pass_oem_change(char *user,
699                          uchar password_encrypted_with_lm_hash[516], 
700                          const uchar old_lm_hash_encrypted[16],
701                          uchar password_encrypted_with_nt_hash[516], 
702                          const uchar old_nt_hash_encrypted[16])
703 {
704         pstring new_passwd;
705         SAM_ACCOUNT *sampass = NULL;
706         NTSTATUS nt_status = check_oem_password(user, password_encrypted_with_lm_hash, 
707                                                 old_lm_hash_encrypted, 
708                                                 password_encrypted_with_nt_hash, 
709                                                 old_nt_hash_encrypted,
710                                                 &sampass, new_passwd, sizeof(new_passwd));
711         
712         if (!NT_STATUS_IS_OK(nt_status))
713                 return nt_status;
714
715         /* We've already checked the old password here.... */
716         become_root();
717         nt_status = change_oem_password(sampass, NULL, new_passwd, True);
718         unbecome_root();
719
720         memset(new_passwd, 0, sizeof(new_passwd));
721
722         pdb_free_sam(&sampass);
723
724         return nt_status;
725 }
726
727 /***********************************************************
728  Decrypt and verify a user password change.  
729
730  The 516 byte long buffers are encrypted with the old NT and 
731  old LM passwords, and if the NT passwords are present, both 
732  buffers contain a unicode string.
733
734  After decrypting the buffers, check the password is correct by
735  matching the old hashed passwords with the passwords in the passdb.
736  
737 ************************************************************/
738
739 static NTSTATUS check_oem_password(const char *user,
740                                    uchar password_encrypted_with_lm_hash[516], 
741                                    const uchar old_lm_hash_encrypted[16],
742                                    uchar password_encrypted_with_nt_hash[516], 
743                                    const uchar old_nt_hash_encrypted[16],
744                                    SAM_ACCOUNT **hnd, char *new_passwd,
745                                    int new_passwd_size)
746 {
747         static uchar null_pw[16];
748         static uchar null_ntpw[16];
749         SAM_ACCOUNT *sampass = NULL;
750         char *password_encrypted;
751         const char *encryption_key;
752         const uint8 *lanman_pw, *nt_pw;
753         uint16 acct_ctrl;
754         uint32 new_pw_len;
755         uchar new_nt_hash[16];
756         uchar old_nt_hash_plain[16];
757         uchar new_lm_hash[16];
758         uchar old_lm_hash_plain[16];
759         char no_pw[2];
760         BOOL ret;
761
762         BOOL nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted);
763         BOOL lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted);
764
765         *hnd = NULL;
766
767         pdb_init_sam(&sampass);
768
769         become_root();
770         ret = pdb_getsampwnam(sampass, user);
771         unbecome_root();
772
773         if (ret == False) {
774                 DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
775                 pdb_free_sam(&sampass);
776                 return NT_STATUS_NO_SUCH_USER; 
777         }
778
779         acct_ctrl = pdb_get_acct_ctrl(sampass);
780         
781         if (acct_ctrl & ACB_DISABLED) {
782                 DEBUG(2,("check_lanman_password: account %s disabled.\n", user));
783                 pdb_free_sam(&sampass);
784                 return NT_STATUS_ACCOUNT_DISABLED;
785         }
786
787         if (acct_ctrl & ACB_PWNOTREQ && lp_null_passwords()) {
788                 /* construct a null password (in case one is needed */
789                 no_pw[0] = 0;
790                 no_pw[1] = 0;
791                 nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
792                 lanman_pw = null_pw;
793                 nt_pw = null_pw;
794
795         } else {
796                 /* save pointers to passwords so we don't have to keep looking them up */
797                 if (lp_lanman_auth()) {
798                         lanman_pw = pdb_get_lanman_passwd(sampass);
799                 } else {
800                         lanman_pw = NULL;
801                 }
802                 nt_pw = pdb_get_nt_passwd(sampass);
803         }
804
805         if (nt_pw && nt_pass_set) {
806                 /* IDEAL Case: passwords are in unicode, and we can
807                  * read use the password encrypted with the NT hash 
808                  */
809                 password_encrypted = password_encrypted_with_nt_hash;
810                 encryption_key = nt_pw;
811         } else if (lanman_pw && lm_pass_set) {
812                 /* password may still be in unicode, but use LM hash version */
813                 password_encrypted = password_encrypted_with_lm_hash;
814                 encryption_key = lanman_pw;
815         } else if (nt_pass_set) {
816                 DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n", 
817                           user));
818                 pdb_free_sam(&sampass);
819                 return NT_STATUS_WRONG_PASSWORD;        
820         } else if (lm_pass_set) {
821                 DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n", 
822                           user));
823                 pdb_free_sam(&sampass);
824                 return NT_STATUS_WRONG_PASSWORD;
825         } else {
826                 DEBUG(1, ("password change requested for user %s, but no password supplied!\n", 
827                           user));
828                 pdb_free_sam(&sampass);
829                 return NT_STATUS_WRONG_PASSWORD;
830         }
831
832         /* 
833          * Decrypt the password with the key 
834          */
835         SamOEMhash( password_encrypted, encryption_key, 516);
836
837         if ( !decode_pw_buffer(password_encrypted, new_passwd, new_passwd_size, &new_pw_len, 
838                                nt_pass_set ? STR_UNICODE : STR_ASCII)) {
839                 pdb_free_sam(&sampass);
840                 return NT_STATUS_WRONG_PASSWORD;
841         }
842
843         /*
844          * To ensure we got the correct new password, hash it and
845          * use it as a key to test the passed old password.
846          */
847
848         if (nt_pass_set) {
849                 /* NT passwords, verify the NT hash. */
850                 
851                 /* Calculate the MD4 hash (NT compatible) of the password */
852                 memset(new_nt_hash, '\0', 16);
853                 E_md4hash(new_passwd, new_nt_hash);
854
855                 if (nt_pw) {
856                         /*
857                          * Now use new_nt_hash as the key to see if the old
858                          * password matches.
859                          */
860                         D_P16(new_nt_hash, old_nt_hash_encrypted, old_nt_hash_plain);
861                         
862                         if (memcmp(nt_pw, old_nt_hash_plain, 16)) {
863                                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
864                                 pdb_free_sam(&sampass);
865                                 return NT_STATUS_WRONG_PASSWORD;
866                         }
867                         
868                         /* We could check the LM password here, but there is
869                          * little point, we already know the password is
870                          * correct, and the LM password might not even be
871                          * present. */
872
873                         /* Further, LM hash generation algorithms
874                          * differ with charset, so we could
875                          * incorrectly fail a perfectly valid password
876                          * change */
877 #ifdef DEBUG_PASSWORD
878                         DEBUG(100,
879                               ("check_oem_password: password %s ok\n", new_passwd));
880 #endif
881                         *hnd = sampass;
882                         return NT_STATUS_OK;
883                 }
884                 
885                 if (lanman_pw) {
886                         /*
887                          * Now use new_nt_hash as the key to see if the old
888                          * LM password matches.
889                          */
890                         D_P16(new_nt_hash, old_lm_hash_encrypted, old_lm_hash_plain);
891                         
892                         if (memcmp(lanman_pw, old_lm_hash_plain, 16)) {
893                                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
894                                 pdb_free_sam(&sampass);
895                                 return NT_STATUS_WRONG_PASSWORD;
896                         }
897 #ifdef DEBUG_PASSWORD
898                         DEBUG(100,
899                               ("check_oem_password: password %s ok\n", new_passwd));
900 #endif
901                         *hnd = sampass;
902                         return NT_STATUS_OK;
903                 }
904         }
905
906         if (lanman_pw && lm_pass_set) {
907
908                 E_deshash(new_passwd, new_lm_hash);
909
910                 /*
911                  * Now use new_lm_hash as the key to see if the old
912                  * password matches.
913                  */
914                 D_P16(new_lm_hash, old_lm_hash_encrypted, old_lm_hash_plain);
915                 
916                 if (memcmp(lanman_pw, old_lm_hash_plain, 16)) {
917                         DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
918                         pdb_free_sam(&sampass);
919                         return NT_STATUS_WRONG_PASSWORD;
920                 }
921                 
922 #ifdef DEBUG_PASSWORD
923                 DEBUG(100,
924                       ("check_oem_password: password %s ok\n", new_passwd));
925 #endif
926                 *hnd = sampass;
927                 return NT_STATUS_OK;
928         }
929
930         /* should not be reached */
931         pdb_free_sam(&sampass);
932         return NT_STATUS_WRONG_PASSWORD;
933 }
934
935 /***********************************************************
936  This routine takes the given password and checks it against
937  the password history. Returns True if this password has been
938  found in the history list.
939 ************************************************************/
940
941 static BOOL check_passwd_history(SAM_ACCOUNT *sampass, const char *plaintext)
942 {
943         uchar new_nt_p16[NT_HASH_LEN];
944         uchar zero_md5_nt_pw[SALTED_MD5_HASH_LEN];
945         const uint8 *nt_pw;
946         const uint8 *pwhistory;
947         BOOL found = False;
948         int i, pwHisLen, curr_pwHisLen;
949
950         account_policy_get(AP_PASSWORD_HISTORY, &pwHisLen);
951         if (pwHisLen == 0) {
952                 return False;
953         }
954
955         pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen);
956         if (!pwhistory || curr_pwHisLen == 0) {
957                 return False;
958         }
959
960         /* Only examine the minimum of the current history len and
961            the stored history len. Avoids race conditions. */
962         pwHisLen = MIN(pwHisLen,curr_pwHisLen);
963
964         nt_pw = pdb_get_nt_passwd(sampass);
965
966         E_md4hash(plaintext, new_nt_p16);
967
968         if (!memcmp(nt_pw, new_nt_p16, NT_HASH_LEN)) {
969                 DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n",
970                         pdb_get_username(sampass) ));
971                 return True;
972         }
973
974         dump_data(100, new_nt_p16, NT_HASH_LEN);
975         dump_data(100, pwhistory, PW_HISTORY_ENTRY_LEN*pwHisLen);
976
977         memset(zero_md5_nt_pw, '\0', SALTED_MD5_HASH_LEN);
978         for (i=0; i<pwHisLen; i++) {
979                 uchar new_nt_pw_salted_md5_hash[SALTED_MD5_HASH_LEN];
980                 const uchar *current_salt = &pwhistory[i*PW_HISTORY_ENTRY_LEN];
981                 const uchar *old_nt_pw_salted_md5_hash = &pwhistory[(i*PW_HISTORY_ENTRY_LEN)+
982                                                         PW_HISTORY_SALT_LEN];
983                 if (!memcmp(zero_md5_nt_pw, old_nt_pw_salted_md5_hash, SALTED_MD5_HASH_LEN)) {
984                         /* Ignore zero valued entries. */
985                         continue;
986                 }
987                 /* Create salted versions of new to compare. */
988                 E_md5hash(current_salt, new_nt_p16, new_nt_pw_salted_md5_hash);
989
990                 if (!memcmp(new_nt_pw_salted_md5_hash, old_nt_pw_salted_md5_hash, SALTED_MD5_HASH_LEN)) {
991                         DEBUG(1,("check_passwd_history: proposed new password for user %s found in history list !\n",
992                                 pdb_get_username(sampass) ));
993                         found = True;
994                         break;
995                 }
996         }
997         return found;
998 }
999
1000 /***********************************************************
1001  Code to change the oem password. Changes both the lanman
1002  and NT hashes.  Old_passwd is almost always NULL.
1003  NOTE this function is designed to be called as root. Check the old password
1004  is correct before calling. JRA.
1005 ************************************************************/
1006
1007 NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd, BOOL as_root)
1008 {
1009         BOOL ret;
1010         uint32 min_len;
1011         struct passwd *pass = NULL;
1012         const char *username = pdb_get_username(hnd);
1013         time_t can_change_time = pdb_get_pass_can_change_time(hnd);
1014
1015         if ((can_change_time != 0) && (time(NULL) < can_change_time)) {
1016                 DEBUG(1, ("user %s cannot change password now, must wait until %s\n", 
1017                           username, http_timestring(can_change_time)));
1018                 return NT_STATUS_ACCOUNT_RESTRICTION;
1019         }
1020
1021         if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen(new_passwd) < min_len)) {
1022                 DEBUG(1, ("user %s cannot change password - password too short\n", 
1023                           username));
1024                 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
1025                 return NT_STATUS_PASSWORD_RESTRICTION;
1026 /*              return NT_STATUS_PWD_TOO_SHORT; */
1027         }
1028
1029         /* Take the passed information and test it for minimum criteria */
1030         /* Minimum password length */
1031         if (strlen(new_passwd) < lp_min_passwd_length()) {
1032                 /* too short, must be at least MINPASSWDLENGTH */
1033                 DEBUG(1, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
1034                        username, lp_min_passwd_length()));
1035                 return NT_STATUS_PASSWORD_RESTRICTION;
1036 /*              return NT_STATUS_PWD_TOO_SHORT; */
1037         }
1038
1039         if (check_passwd_history(hnd,new_passwd)) {
1040                 return NT_STATUS_PASSWORD_RESTRICTION;
1041         }
1042
1043         pass = Get_Pwnam(username);
1044         if (!pass) {
1045                 DEBUG(1, ("check_oem_password: Username %s does not exist in system !?!\n", username));
1046                 return NT_STATUS_ACCESS_DENIED;
1047         }
1048
1049         /* Use external script to check password complexity */
1050         if (lp_check_password_script()) {
1051                 int check_ret;
1052
1053                 check_ret = smbrunsecret(lp_check_password_script(), new_passwd);
1054                 DEBUG(5, ("change_oem_password: check password script (%s) returned [%d]\n", lp_check_password_script(), check_ret));
1055
1056                 if (check_ret != 0) {
1057                         DEBUG(1, ("change_oem_password: check password script said new password is not good enough!\n"));
1058                         return NT_STATUS_PASSWORD_RESTRICTION;
1059                 }
1060         }
1061
1062         /*
1063          * If unix password sync was requested, attempt to change
1064          * the /etc/passwd database first. Return failure if this cannot
1065          * be done.
1066          *
1067          * This occurs before the oem change, because we don't want to
1068          * update it if chgpasswd failed.
1069          *
1070          * Conditional on lp_unix_password_sync() because we don't want
1071          * to touch the unix db unless we have admin permission.
1072          */
1073         
1074         if(lp_unix_password_sync() &&
1075                 !chgpasswd(username, pass, old_passwd, new_passwd, as_root)) {
1076                 return NT_STATUS_ACCESS_DENIED;
1077         }
1078
1079         if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
1080                 return NT_STATUS_ACCESS_DENIED;
1081         }
1082
1083         /* Now write it into the file. */
1084         ret = pdb_update_sam_account (hnd);
1085
1086         if (!ret) {
1087                 return NT_STATUS_ACCESS_DENIED;
1088         }
1089         
1090         return NT_STATUS_OK;
1091 }