spelling
[ira/wip.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-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                 timeout = 2000;
249                 nread = 0;
250                 buffer[nread] = 0;
251
252                 while ((len = read_socket_with_timeout(master, buffer + nread, 1,
253                                                        sizeof(buffer) - nread - 1,
254                                                        timeout)) > 0) {
255                         nread += len;
256                         buffer[nread] = 0;
257
258                         {
259                                 /* Eat leading/trailing whitespace before match. */
260                                 pstring str;
261                                 pstrcpy( str, buffer);
262                                 trim_string( str, " ", " ");
263
264                                 if ((match = (unix_wild_match(expected, str) == 0)))
265                                         timeout = 200;
266                         }
267                 }
268
269                 if (lp_passwd_chat_debug())
270                         DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
271                                     expected, buffer, match ? "yes" : "no" ));
272
273                 if (match)
274                         break;
275
276                 if (len < 0) {
277                         DEBUG(2, ("expect: %s\n", strerror(errno)));
278                         return False;
279                 }
280         }
281
282         DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
283         return match;
284 }
285
286 static void pwd_sub(char *buf)
287 {
288         all_string_sub(buf, "\\n", "\n", 0);
289         all_string_sub(buf, "\\r", "\r", 0);
290         all_string_sub(buf, "\\s", " ", 0);
291         all_string_sub(buf, "\\t", "\t", 0);
292 }
293
294 static int talktochild(int master, const char *seq)
295 {
296         int count = 0;
297         fstring issue, expected;
298
299         fstrcpy(issue, ".");
300
301         while (next_token(&seq, expected, NULL, sizeof(expected)))
302         {
303                 pwd_sub(expected);
304                 count++;
305
306                 if (!expect(master, issue, expected))
307                 {
308                         DEBUG(3, ("Response %d incorrect\n", count));
309                         return False;
310                 }
311
312                 if (!next_token(&seq, issue, NULL, sizeof(issue)))
313                         fstrcpy(issue, ".");
314
315                 pwd_sub(issue);
316         }
317         if (!strequal(issue, ".")) {
318                 /* we have one final issue to send */
319                 fstrcpy(expected, ".");
320                 if (!expect(master, issue, expected))
321                         return False;
322         }
323
324         return (count > 0);
325 }
326
327 static BOOL chat_with_program(char *passwordprogram, struct passwd *pass,
328                               char *chatsequence, BOOL as_root)
329 {
330         char *slavedev;
331         int master;
332         pid_t pid, wpid;
333         int wstat;
334         BOOL chstat = False;
335
336         if (pass == NULL)
337         {
338                 DEBUG(0,
339                       ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
340                 return False;
341         }
342
343         /* allocate a pseudo-terminal device */
344         if ((master = findpty(&slavedev)) < 0)
345         {
346                 DEBUG(3,
347                       ("Cannot Allocate pty for password change: %s\n",
348                        pass->pw_name));
349                 return (False);
350         }
351
352         /*
353          * We need to temporarily stop CatchChild from eating
354          * SIGCLD signals as it also eats the exit status code. JRA.
355          */
356
357         CatchChildLeaveStatus();
358
359         if ((pid = sys_fork()) < 0)
360         {
361                 DEBUG(3,
362                       ("Cannot fork() child for password change: %s\n",
363                        pass->pw_name));
364                 close(master);
365                 CatchChild();
366                 return (False);
367         }
368
369         /* we now have a pty */
370         if (pid > 0)
371         {                       /* This is the parent process */
372                 if ((chstat = talktochild(master, chatsequence)) == False)
373                 {
374                         DEBUG(3,
375                               ("Child failed to change password: %s\n",
376                                pass->pw_name));
377                         kill(pid, SIGKILL);     /* be sure to end this process */
378                 }
379
380                 while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0)
381                 {
382                         if (errno == EINTR)
383                         {
384                                 errno = 0;
385                                 continue;
386                         }
387                         break;
388                 }
389
390                 if (wpid < 0)
391                 {
392                         DEBUG(3, ("The process is no longer waiting!\n\n"));
393                         close(master);
394                         CatchChild();
395                         return (False);
396                 }
397
398                 /*
399                  * Go back to ignoring children.
400                  */
401                 CatchChild();
402
403                 close(master);
404
405                 if (pid != wpid)
406                 {
407                         DEBUG(3,
408                               ("We were waiting for the wrong process ID\n"));
409                         return (False);
410                 }
411                 if (WIFEXITED(wstat) == 0)
412                 {
413                         DEBUG(3,
414                               ("The process exited while we were waiting\n"));
415                         return (False);
416                 }
417                 if (WEXITSTATUS(wstat) != 0)
418                 {
419                         DEBUG(3,
420                               ("The status of the process exiting was %d\n",
421                                wstat));
422                         return (False);
423                 }
424
425         }
426         else
427         {
428                 /* CHILD */
429
430                 /*
431                  * Lose any oplock capabilities.
432                  */
433                 oplock_set_capability(False, False);
434
435                 /* make sure it doesn't freeze */
436                 alarm(20);
437
438                 if (as_root)
439                         become_root();
440
441                 DEBUG(3,
442                       ("Dochild for user %s (uid=%d,gid=%d)\n", pass->pw_name,
443                        (int)getuid(), (int)getgid()));
444                 chstat =
445                         dochild(master, slavedev, pass, passwordprogram,
446                                 as_root);
447
448                 if (as_root)
449                         unbecome_root();
450
451                 /*
452                  * The child should never return from dochild() ....
453                  */
454
455                 DEBUG(0,
456                       ("chat_with_program: Error: dochild() returned %d\n",
457                        chstat));
458                 exit(1);
459         }
460
461         if (chstat)
462                 DEBUG(3,
463                       ("Password change %ssuccessful for user %s\n",
464                        (chstat ? "" : "un"), pass->pw_name));
465         return (chstat);
466 }
467
468
469 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
470 {
471         pstring passwordprogram;
472         pstring chatsequence;
473         size_t i;
474         size_t len;
475
476         struct passwd *pass;
477
478         if (!name) {
479                 DEBUG(1, ("NULL username specfied to chgpasswd()!\n"));
480         }
481         
482         pass = Get_Pwnam(name);
483         if (!pass) {
484                 DEBUG(1, ("Username does not exist in system passwd!\n"));
485                 return False;
486         }
487
488         if (!oldpass) {
489                 oldpass = "";
490         }
491
492         DEBUG(3, ("Password change for user: %s\n", name));
493
494 #if DEBUG_PASSWORD
495         DEBUG(100, ("Passwords: old=%s new=%s\n", oldpass, newpass));
496 #endif
497
498         /* Take the passed information and test it for minimum criteria */
499         /* Minimum password length */
500         if (strlen(newpass) < lp_min_passwd_length()) {
501                 /* too short, must be at least MINPASSWDLENGTH */
502                 DEBUG(0, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
503                        name, lp_min_passwd_length()));
504                 return (False); /* inform the user */
505         }
506
507         /* Password is same as old password */
508         if (strcmp(oldpass, newpass) == 0) {
509                 /* don't allow same password */
510                 DEBUG(2, ("Password Change: %s, New password is same as old\n", name)); /* log the attempt */
511                 return (False); /* inform the user */
512         }
513
514         /* 
515          * Check the old and new passwords don't contain any control
516          * characters.
517          */
518
519         len = strlen(oldpass);
520         for (i = 0; i < len; i++) {
521                 if (iscntrl((int)oldpass[i])) {
522                         DEBUG(0,
523                               ("chat_with_program: oldpass contains control characters (disallowed).\n"));
524                         return False;
525                 }
526         }
527
528         len = strlen(newpass);
529         for (i = 0; i < len; i++) {
530                 if (iscntrl((int)newpass[i])) {
531                         DEBUG(0,
532                               ("chat_with_program: newpass contains control characters (disallowed).\n"));
533                         return False;
534                 }
535         }
536         
537 #ifdef WITH_PAM
538         if (lp_pam_password_change()) {
539                 BOOL ret;
540
541                 if (as_root)
542                         become_root();
543
544                 if (pass) {
545                         ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
546                 } else {
547                         ret = smb_pam_passchange(name, oldpass, newpass);
548                 }
549                         
550                 if (as_root)
551                         unbecome_root();
552
553                 return ret;
554         }
555 #endif
556
557         /* A non-PAM password change just doen't make sense without a valid local user */
558
559         if (pass == NULL)
560         {
561                 DEBUG(0,
562                       ("chgpasswd: user %s doesn't exist in the UNIX password database.\n",
563                        name));
564                 return False;
565         }
566
567         pstrcpy(passwordprogram, lp_passwd_program());
568         pstrcpy(chatsequence, lp_passwd_chat());
569
570         if (!*chatsequence) {
571                 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
572                 return (False);
573         }
574
575         if (!*passwordprogram) {
576                 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
577                 return (False);
578         }
579
580         if (as_root) {
581                 /* The password program *must* contain the user name to work. Fail if not. */
582                 if (strstr(passwordprogram, "%u") == NULL) {
583                         DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
584 the string %%u, and the given string %s does not.\n", passwordprogram ));
585                         return False;
586                 }
587         }
588
589         pstring_sub(passwordprogram, "%u", name);
590         /* note that we do NOT substitute the %o and %n in the password program
591            as this would open up a security hole where the user could use
592            a new password containing shell escape characters */
593
594         pstring_sub(chatsequence, "%u", name);
595         all_string_sub(chatsequence, "%o", oldpass, sizeof(pstring));
596         all_string_sub(chatsequence, "%n", newpass, sizeof(pstring));
597         return (chat_with_program
598                 (passwordprogram, pass, chatsequence, as_root));
599 }
600
601 #else /* ALLOW_CHANGE_PASSWORD */
602
603 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
604 {
605         DEBUG(0, ("Password changing not compiled in (user=%s)\n", name));
606         return (False);
607 }
608 #endif /* ALLOW_CHANGE_PASSWORD */
609
610 /***********************************************************
611  Code to check the lanman hashed password.
612 ************************************************************/
613
614 BOOL check_lanman_password(char *user, uchar * pass1,
615                            uchar * pass2, SAM_ACCOUNT **hnd)
616 {
617         uchar unenc_new_pw[16];
618         uchar unenc_old_pw[16];
619         SAM_ACCOUNT *sampass = NULL;
620         uint16 acct_ctrl;
621         const uint8 *lanman_pw;
622         BOOL ret;
623         
624         become_root();
625         ret = pdb_getsampwnam(sampass, user);
626         unbecome_root();
627
628         if (ret == False) {
629                 DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n"));
630                 pdb_free_sam(&sampass);
631                 return False;
632         }
633         
634         acct_ctrl = pdb_get_acct_ctrl     (sampass);
635         lanman_pw = pdb_get_lanman_passwd (sampass);
636
637         if (acct_ctrl & ACB_DISABLED) {
638                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
639                 pdb_free_sam(&sampass);
640                 return False;
641         }
642
643         if (lanman_pw == NULL) {
644                 if (acct_ctrl & ACB_PWNOTREQ) {
645                         /* this saves the pointer for the caller */
646                         *hnd = sampass;
647                         return True;
648                 } else {
649                         DEBUG(0, ("check_lanman_password: no lanman password !\n"));
650                         pdb_free_sam(&sampass);
651                         return False;
652                 }
653         }
654
655         /* Get the new lanman hash. */
656         D_P16(lanman_pw, pass2, unenc_new_pw);
657
658         /* Use this to get the old lanman hash. */
659         D_P16(unenc_new_pw, pass1, unenc_old_pw);
660
661         /* Check that the two old passwords match. */
662         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
663                 DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
664                 pdb_free_sam(&sampass);
665                 return False;
666         }
667
668         /* this saves the pointer for the caller */
669         *hnd = sampass;
670         return True;
671 }
672
673 /***********************************************************
674  Code to change the lanman hashed password.
675  It nulls out the NT hashed password as it will
676  no longer be valid.
677 ************************************************************/
678
679 BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2)
680 {
681         static uchar null_pw[16];
682         uchar unenc_new_pw[16];
683         BOOL ret;
684         uint16 acct_ctrl;
685         const uint8 *pwd;
686
687         if (sampass == NULL) {
688                 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
689                 return False;
690         }
691         
692         acct_ctrl = pdb_get_acct_ctrl(sampass);
693         pwd = pdb_get_lanman_passwd(sampass);
694
695         if (acct_ctrl & ACB_DISABLED) {
696                 DEBUG(0,("change_lanman_password: account %s disabled.\n",
697                        pdb_get_username(sampass)));
698                 return False;
699         }
700
701         if (pwd == NULL) { 
702                 if (acct_ctrl & ACB_PWNOTREQ) {
703                         uchar no_pw[14];
704                         memset(no_pw, '\0', 14);
705                         E_P16(no_pw, null_pw);
706
707                         /* Get the new lanman hash. */
708                         D_P16(null_pw, pass2, unenc_new_pw);
709                 } else {
710                         DEBUG(0,("change_lanman_password: no lanman password !\n"));
711                         return False;
712                 }
713         } else {
714                 /* Get the new lanman hash. */
715                 D_P16(pwd, pass2, unenc_new_pw);
716         }
717
718         if (!pdb_set_lanman_passwd(sampass, unenc_new_pw, PDB_CHANGED)) {
719                 return False;
720         }
721
722         if (!pdb_set_nt_passwd    (sampass, NULL, PDB_CHANGED)) {
723                 return False;   /* We lose the NT hash. Sorry. */
724         }
725
726         if (!pdb_set_pass_changed_now  (sampass)) {
727                 pdb_free_sam(&sampass);
728                 /* Not quite sure what this one qualifies as, but this will do */
729                 return False; 
730         }
731  
732         /* Now flush the sam_passwd struct to persistent storage */
733         become_root();
734         ret = pdb_update_sam_account (sampass);
735         unbecome_root();
736
737         return ret;
738 }
739
740 /***********************************************************
741  Code to check and change the OEM hashed password.
742 ************************************************************/
743 NTSTATUS pass_oem_change(char *user,
744                          uchar * lmdata, uchar * lmhash,
745                          uchar * ntdata, uchar * nthash)
746 {
747         fstring new_passwd;
748         const char *unix_user;
749         SAM_ACCOUNT *sampass = NULL;
750         NTSTATUS nt_status 
751                 = check_oem_password(user, lmdata, lmhash, ntdata, nthash,
752                                      &sampass, new_passwd, sizeof(new_passwd));
753
754         if (!NT_STATUS_IS_OK(nt_status))
755                 return nt_status;
756
757         /* 
758          * At this point we have the new case-sensitive plaintext
759          * password in the fstring new_passwd. If we wanted to synchronise
760          * with UNIX passwords we would call a UNIX password changing 
761          * function here. However it would have to be done as root
762          * as the plaintext of the old users password is not 
763          * available. JRA.
764          */
765
766         unix_user = pdb_get_username(sampass);
767
768         nt_status = change_oem_password(sampass, NULL, new_passwd);
769
770         memset(new_passwd, 0, sizeof(new_passwd));
771
772         pdb_free_sam(&sampass);
773
774         return nt_status;
775 }
776
777 /***********************************************************
778  Code to check the OEM hashed password.
779
780  this function ignores the 516 byte nt OEM hashed password
781  but does use the lm OEM password to check the nt hashed-hash.
782
783 ************************************************************/
784
785 static NTSTATUS check_oem_password(const char *user,
786                                uchar * lmdata, const uchar * lmhash,
787                                const uchar * ntdata, const uchar * nthash,
788                                SAM_ACCOUNT **hnd, char *new_passwd,
789                                int new_passwd_size)
790 {
791         static uchar null_pw[16];
792         static uchar null_ntpw[16];
793         SAM_ACCOUNT *sampass = NULL;
794         const uint8 *lanman_pw, *nt_pw;
795         uint16 acct_ctrl;
796         int new_pw_len;
797         uchar new_ntp16[16];
798         uchar unenc_old_ntpw[16];
799         uchar new_p16[16];
800         uchar unenc_old_pw[16];
801         char no_pw[2];
802         BOOL ret;
803
804         BOOL nt_pass_set = (ntdata != NULL && nthash != NULL);
805
806         *hnd = NULL;
807
808         pdb_init_sam(&sampass);
809
810         become_root();
811         ret = pdb_getsampwnam(sampass, user);
812         unbecome_root();
813
814         if (ret == False) {
815                 DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
816                 pdb_free_sam(&sampass);
817                 return NT_STATUS_WRONG_PASSWORD;
818                 /*
819                   TODO: check what Win2k returns for this:
820                   return NT_STATUS_NO_SUCH_USER; 
821                 */
822         }
823
824         acct_ctrl = pdb_get_acct_ctrl(sampass);
825         
826         if (acct_ctrl & ACB_DISABLED) {
827                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
828                 pdb_free_sam(&sampass);
829                 return NT_STATUS_ACCOUNT_DISABLED;
830         }
831
832         /* construct a null password (in case one is needed */
833         no_pw[0] = 0;
834         no_pw[1] = 0;
835         nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
836
837         /* save pointers to passwords so we don't have to keep looking them up */
838         lanman_pw = pdb_get_lanman_passwd(sampass);
839         nt_pw = pdb_get_nt_passwd(sampass);
840
841         /* check for null passwords */
842         if (lanman_pw == NULL) {
843                 if (!(acct_ctrl & ACB_PWNOTREQ)) {
844                         DEBUG(0,("check_oem_password: no lanman password !\n"));
845                         pdb_free_sam(&sampass);
846                         return NT_STATUS_WRONG_PASSWORD;
847                 }
848         }
849         
850         if (pdb_get_nt_passwd(sampass) == NULL && nt_pass_set) {
851                 if (!(acct_ctrl & ACB_PWNOTREQ)) {
852                         DEBUG(0,("check_oem_password: no ntlm password !\n"));
853                         pdb_free_sam(&sampass);
854                         return NT_STATUS_WRONG_PASSWORD;
855                 }
856         }
857         
858         /* 
859          * Call the hash function to get the new password.
860          */
861         SamOEMhash( lmdata, lanman_pw, 516);
862
863         /* 
864          * The length of the new password is in the last 4 bytes of
865          * the data buffer.
866          */
867
868         new_pw_len = IVAL(lmdata, 512);
869
870         if (new_pw_len < 0 || new_pw_len > new_passwd_size - 1) {
871                 DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len));
872                 pdb_free_sam(&sampass);
873                 return NT_STATUS_WRONG_PASSWORD;
874         }
875
876         if (nt_pass_set) {
877                 /*
878                  * nt passwords are in unicode
879                  */
880                 pull_ucs2(NULL, new_passwd, 
881                           (const smb_ucs2_t *)&lmdata[512 - new_pw_len],
882                           new_passwd_size, new_pw_len, 0);
883         } else {
884                 memcpy(new_passwd, &lmdata[512 - new_pw_len], new_pw_len);
885                 new_passwd[new_pw_len] = 0;
886         }
887
888         /*
889          * To ensure we got the correct new password, hash it and
890          * use it as a key to test the passed old password.
891          */
892
893         nt_lm_owf_gen(new_passwd, new_ntp16, new_p16);
894
895         if (!nt_pass_set) {
896                 /*
897                  * Now use new_p16 as the key to see if the old
898                  * password matches.
899                  */
900                 D_P16(new_p16, lmhash, unenc_old_pw);
901
902                 if (memcmp(lanman_pw, unenc_old_pw, 16)) {
903                         DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
904                         pdb_free_sam(&sampass);
905                         return NT_STATUS_WRONG_PASSWORD;
906                 }
907
908 #ifdef DEBUG_PASSWORD
909                 DEBUG(100,
910                       ("check_oem_password: password %s ok\n", new_passwd));
911 #endif
912                 *hnd = sampass;
913                 return NT_STATUS_OK;
914         }
915
916         /*
917          * Now use new_p16 as the key to see if the old
918          * password matches.
919          */
920         D_P16(new_ntp16, lmhash, unenc_old_pw);
921         D_P16(new_ntp16, nthash, unenc_old_ntpw);
922
923         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
924                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
925                 pdb_free_sam(&sampass);
926                 return NT_STATUS_WRONG_PASSWORD;
927         }
928
929         if (memcmp(nt_pw, unenc_old_ntpw, 16)) {
930                 DEBUG(0,("check_oem_password: old nt password doesn't match.\n"));
931                 pdb_free_sam(&sampass);
932                 return NT_STATUS_WRONG_PASSWORD;
933         }
934 #ifdef DEBUG_PASSWORD
935         DEBUG(100, ("check_oem_password: password %s ok\n", new_passwd));
936 #endif
937
938         *hnd = sampass;
939         return NT_STATUS_OK;
940 }
941
942 /***********************************************************
943  Code to change the oem password. Changes both the lanman
944  and NT hashes.  Old_passwd is almost always NULL.
945 ************************************************************/
946
947 NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd)
948 {
949         BOOL ret;
950         uint32 min_len;
951
952         if (time(NULL) < pdb_get_pass_can_change_time(hnd)) {
953                 DEBUG(1, ("user %s cannot change password now, must wait until %s\n", 
954                           pdb_get_username(hnd), http_timestring(pdb_get_pass_can_change_time(hnd))));
955                 return NT_STATUS_PASSWORD_RESTRICTION;
956         }
957
958         if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen(new_passwd) < min_len)) {
959                 DEBUG(1, ("user %s cannot change password - password too short\n", 
960                           pdb_get_username(hnd)));
961                 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
962                 return NT_STATUS_PASSWORD_RESTRICTION;
963 /*              return NT_STATUS_PWD_TOO_SHORT; */
964         }
965
966         /* Take the passed information and test it for minimum criteria */
967         /* Minimum password length */
968         if (strlen(new_passwd) < lp_min_passwd_length()) {
969                 /* too short, must be at least MINPASSWDLENGTH */
970                 DEBUG(1, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
971                        pdb_get_username(hnd), lp_min_passwd_length()));
972                 return NT_STATUS_PASSWORD_RESTRICTION;
973 /*              return NT_STATUS_PWD_TOO_SHORT; */
974         }
975
976         /* TODO:  Add cracklib support here */
977
978         /*
979          * If unix password sync was requested, attempt to change
980          * the /etc/passwd database first. Return failure if this cannot
981          * be done.
982          *
983          * This occurs before the oem change, because we don't want to
984          * update it if chgpasswd failed.
985          *
986          * Conditional on lp_unix_password_sync() because we don't want
987          * to touch the unix db unless we have admin permission.
988          */
989         
990         if(lp_unix_password_sync() &&
991                 !chgpasswd(pdb_get_username(hnd), old_passwd, new_passwd, False)) {
992                 return NT_STATUS_ACCESS_DENIED;
993         }
994
995         if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
996                 return NT_STATUS_ACCESS_DENIED;
997         }
998
999         /* Now write it into the file. */
1000         become_root();
1001         ret = pdb_update_sam_account (hnd);
1002         unbecome_root();
1003
1004         if (!ret) {
1005                 return NT_STATUS_ACCESS_DENIED;
1006         }
1007         
1008         return NT_STATUS_OK;
1009 }