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