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