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