port latest changes from SAMBA_3_0 tree
[tprouty/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         {
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  NOTE this function is designed to be called as root. Check the old password
678  is correct before calling. JRA.
679 ************************************************************/
680
681 BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2)
682 {
683         static uchar null_pw[16];
684         uchar unenc_new_pw[16];
685         BOOL ret;
686         uint16 acct_ctrl;
687         const uint8 *pwd;
688
689         if (sampass == NULL) {
690                 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
691                 return False;
692         }
693         
694         acct_ctrl = pdb_get_acct_ctrl(sampass);
695         pwd = pdb_get_lanman_passwd(sampass);
696
697         if (acct_ctrl & ACB_DISABLED) {
698                 DEBUG(0,("change_lanman_password: account %s disabled.\n",
699                        pdb_get_username(sampass)));
700                 return False;
701         }
702
703         if (pwd == NULL) { 
704                 if (acct_ctrl & ACB_PWNOTREQ) {
705                         uchar no_pw[14];
706                         memset(no_pw, '\0', 14);
707                         E_P16(no_pw, null_pw);
708
709                         /* Get the new lanman hash. */
710                         D_P16(null_pw, pass2, unenc_new_pw);
711                 } else {
712                         DEBUG(0,("change_lanman_password: no lanman password !\n"));
713                         return False;
714                 }
715         } else {
716                 /* Get the new lanman hash. */
717                 D_P16(pwd, pass2, unenc_new_pw);
718         }
719
720         if (!pdb_set_lanman_passwd(sampass, unenc_new_pw, PDB_CHANGED)) {
721                 return False;
722         }
723
724         if (!pdb_set_nt_passwd    (sampass, NULL, PDB_CHANGED)) {
725                 return False;   /* We lose the NT hash. Sorry. */
726         }
727
728         if (!pdb_set_pass_changed_now  (sampass)) {
729                 pdb_free_sam(&sampass);
730                 /* Not quite sure what this one qualifies as, but this will do */
731                 return False; 
732         }
733  
734         /* Now flush the sam_passwd struct to persistent storage */
735         ret = pdb_update_sam_account (sampass);
736
737         return ret;
738 }
739
740 /***********************************************************
741  Code to check and change the OEM hashed password.
742 ************************************************************/
743
744 NTSTATUS pass_oem_change(char *user,
745                          uchar * lmdata, uchar * lmhash,
746                          uchar * ntdata, uchar * nthash)
747 {
748         fstring new_passwd;
749         const char *unix_user;
750         SAM_ACCOUNT *sampass = NULL;
751         NTSTATUS nt_status = 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         /* We've already checked the old password here.... */
769         become_root();
770         nt_status = change_oem_password(sampass, NULL, new_passwd);
771         unbecome_root();
772
773         memset(new_passwd, 0, sizeof(new_passwd));
774
775         pdb_free_sam(&sampass);
776
777         return nt_status;
778 }
779
780 /***********************************************************
781  Code to check the OEM hashed password.
782
783  this function ignores the 516 byte nt OEM hashed password
784  but does use the lm OEM password to check the nt hashed-hash.
785
786 ************************************************************/
787
788 static NTSTATUS check_oem_password(const char *user,
789                                uchar * lmdata, const uchar * lmhash,
790                                const uchar * ntdata, const uchar * nthash,
791                                SAM_ACCOUNT **hnd, char *new_passwd,
792                                int new_passwd_size)
793 {
794         static uchar null_pw[16];
795         static uchar null_ntpw[16];
796         SAM_ACCOUNT *sampass = NULL;
797         const uint8 *lanman_pw, *nt_pw;
798         uint16 acct_ctrl;
799         int new_pw_len;
800         uchar new_ntp16[16];
801         uchar unenc_old_ntpw[16];
802         uchar new_p16[16];
803         uchar unenc_old_pw[16];
804         char no_pw[2];
805         BOOL ret;
806
807         BOOL nt_pass_set = (ntdata != NULL && nthash != NULL);
808
809         *hnd = NULL;
810
811         pdb_init_sam(&sampass);
812
813         become_root();
814         ret = pdb_getsampwnam(sampass, user);
815         unbecome_root();
816
817         if (ret == False) {
818                 DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
819                 pdb_free_sam(&sampass);
820                 return NT_STATUS_WRONG_PASSWORD;
821                 /*
822                   TODO: check what Win2k returns for this:
823                   return NT_STATUS_NO_SUCH_USER; 
824                 */
825         }
826
827         acct_ctrl = pdb_get_acct_ctrl(sampass);
828         
829         if (acct_ctrl & ACB_DISABLED) {
830                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
831                 pdb_free_sam(&sampass);
832                 return NT_STATUS_ACCOUNT_DISABLED;
833         }
834
835         /* construct a null password (in case one is needed */
836         no_pw[0] = 0;
837         no_pw[1] = 0;
838         nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
839
840         /* save pointers to passwords so we don't have to keep looking them up */
841         lanman_pw = pdb_get_lanman_passwd(sampass);
842         nt_pw = pdb_get_nt_passwd(sampass);
843
844         /* check for null passwords */
845         if (lanman_pw == NULL) {
846                 if (!(acct_ctrl & ACB_PWNOTREQ)) {
847                         DEBUG(0,("check_oem_password: no lanman password !\n"));
848                         pdb_free_sam(&sampass);
849                         return NT_STATUS_WRONG_PASSWORD;
850                 }
851         }
852         
853         if (pdb_get_nt_passwd(sampass) == NULL && nt_pass_set) {
854                 if (!(acct_ctrl & ACB_PWNOTREQ)) {
855                         DEBUG(0,("check_oem_password: no ntlm password !\n"));
856                         pdb_free_sam(&sampass);
857                         return NT_STATUS_WRONG_PASSWORD;
858                 }
859         }
860         
861         /* 
862          * Call the hash function to get the new password.
863          */
864         SamOEMhash( lmdata, lanman_pw, 516);
865
866         /* 
867          * The length of the new password is in the last 4 bytes of
868          * the data buffer.
869          */
870
871         new_pw_len = IVAL(lmdata, 512);
872
873         if (new_pw_len < 0 || new_pw_len > new_passwd_size - 1) {
874                 DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len));
875                 pdb_free_sam(&sampass);
876                 return NT_STATUS_WRONG_PASSWORD;
877         }
878
879         if (nt_pass_set) {
880                 /*
881                  * nt passwords are in unicode
882                  */
883                 pull_ucs2(NULL, new_passwd, 
884                           (const smb_ucs2_t *)&lmdata[512 - new_pw_len],
885                           new_passwd_size, new_pw_len, 0);
886         } else {
887                 memcpy(new_passwd, &lmdata[512 - new_pw_len], new_pw_len);
888                 new_passwd[new_pw_len] = 0;
889         }
890
891         /*
892          * To ensure we got the correct new password, hash it and
893          * use it as a key to test the passed old password.
894          */
895
896         nt_lm_owf_gen(new_passwd, new_ntp16, new_p16);
897
898         if (!nt_pass_set) {
899                 /*
900                  * Now use new_p16 as the key to see if the old
901                  * password matches.
902                  */
903                 D_P16(new_p16, lmhash, unenc_old_pw);
904
905                 if (memcmp(lanman_pw, unenc_old_pw, 16)) {
906                         DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
907                         pdb_free_sam(&sampass);
908                         return NT_STATUS_WRONG_PASSWORD;
909                 }
910
911 #ifdef DEBUG_PASSWORD
912                 DEBUG(100,
913                       ("check_oem_password: password %s ok\n", new_passwd));
914 #endif
915                 *hnd = sampass;
916                 return NT_STATUS_OK;
917         }
918
919         /*
920          * Now use new_p16 as the key to see if the old
921          * password matches.
922          */
923         D_P16(new_ntp16, lmhash, unenc_old_pw);
924         D_P16(new_ntp16, nthash, unenc_old_ntpw);
925
926         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
927                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
928                 pdb_free_sam(&sampass);
929                 return NT_STATUS_WRONG_PASSWORD;
930         }
931
932         if (memcmp(nt_pw, unenc_old_ntpw, 16)) {
933                 DEBUG(0,("check_oem_password: old nt password doesn't match.\n"));
934                 pdb_free_sam(&sampass);
935                 return NT_STATUS_WRONG_PASSWORD;
936         }
937 #ifdef DEBUG_PASSWORD
938         DEBUG(100, ("check_oem_password: password %s ok\n", new_passwd));
939 #endif
940
941         *hnd = sampass;
942         return NT_STATUS_OK;
943 }
944
945 /***********************************************************
946  Code to change the oem password. Changes both the lanman
947  and NT hashes.  Old_passwd is almost always NULL.
948  NOTE this function is designed to be called as root. Check the old password
949  is correct before calling. JRA.
950 ************************************************************/
951
952 NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd)
953 {
954         BOOL ret;
955         uint32 min_len;
956
957         if (time(NULL) < pdb_get_pass_can_change_time(hnd)) {
958                 DEBUG(1, ("user %s cannot change password now, must wait until %s\n", 
959                           pdb_get_username(hnd), http_timestring(pdb_get_pass_can_change_time(hnd))));
960                 return NT_STATUS_PASSWORD_RESTRICTION;
961         }
962
963         if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen(new_passwd) < min_len)) {
964                 DEBUG(1, ("user %s cannot change password - password too short\n", 
965                           pdb_get_username(hnd)));
966                 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
967                 return NT_STATUS_PASSWORD_RESTRICTION;
968 /*              return NT_STATUS_PWD_TOO_SHORT; */
969         }
970
971         /* Take the passed information and test it for minimum criteria */
972         /* Minimum password length */
973         if (strlen(new_passwd) < lp_min_passwd_length()) {
974                 /* too short, must be at least MINPASSWDLENGTH */
975                 DEBUG(1, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
976                        pdb_get_username(hnd), lp_min_passwd_length()));
977                 return NT_STATUS_PASSWORD_RESTRICTION;
978 /*              return NT_STATUS_PWD_TOO_SHORT; */
979         }
980
981         /* TODO:  Add cracklib support here */
982
983         /*
984          * If unix password sync was requested, attempt to change
985          * the /etc/passwd database first. Return failure if this cannot
986          * be done.
987          *
988          * This occurs before the oem change, because we don't want to
989          * update it if chgpasswd failed.
990          *
991          * Conditional on lp_unix_password_sync() because we don't want
992          * to touch the unix db unless we have admin permission.
993          */
994         
995         if(lp_unix_password_sync() &&
996                 !chgpasswd(pdb_get_username(hnd), old_passwd, new_passwd, False)) {
997                 return NT_STATUS_ACCESS_DENIED;
998         }
999
1000         if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
1001                 return NT_STATUS_ACCESS_DENIED;
1002         }
1003
1004         /* Now write it into the file. */
1005         ret = pdb_update_sam_account (hnd);
1006
1007         if (!ret) {
1008                 return NT_STATUS_ACCESS_DENIED;
1009         }
1010         
1011         return NT_STATUS_OK;
1012 }