Merge from HEAD - eliminated unused arguments.
[kai/samba.git] / source / smbd / chgpasswd.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Samba utility functions
4    Copyright (C) Andrew Tridgell 1992-1998
5    Copyright (C) Andrew Bartlett 2001-2002
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 /* fork a child process to exec passwd and write to its
23  * tty to change a users password. This is running as the
24  * user who is attempting to change the password.
25  */
26
27 /* 
28  * This code was copied/borrowed and stolen from various sources.
29  * The primary source was the poppasswd.c from the authors of POPMail. This software
30  * was included as a client to change passwords using the 'passwd' program
31  * on the remote machine.
32  *
33  * This routine is called by set_user_password() in password.c only if ALLOW_PASSWORD_CHANGE
34  * is defined in the compiler directives located in the Makefile.
35  *
36  * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
37  * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
38  * and rights to modify, distribute or incorporate this change to the CAP suite or
39  * using it for any other reason are granted, so long as this disclaimer is left intact.
40  */
41
42 /*
43    This code was hacked considerably for inclusion in Samba, primarily
44    by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
45    of the "password chat" option, which allows the easy runtime
46    specification of the expected sequence of events to change a
47    password.
48    */
49
50 #include "includes.h"
51
52 extern struct passdb_ops pdb_ops;
53
54 static NTSTATUS check_oem_password(const char *user,
55                                uchar * lmdata, const uchar * lmhash,
56                                const uchar * ntdata, const uchar * nthash,
57                                SAM_ACCOUNT **hnd, char *new_passwd,
58                                int new_passwd_size);
59
60 #if ALLOW_CHANGE_PASSWORD
61
62 static int findpty(char **slave)
63 {
64         int master;
65         static fstring line;
66         DIR *dirp;
67         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_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         if (!oldpass) {
483                 oldpass = "";
484         }
485
486         DEBUG(3, ("Password change for user: %s\n", name));
487
488 #if DEBUG_PASSWORD
489         DEBUG(100, ("Passwords: old=%s new=%s\n", oldpass, newpass));
490 #endif
491
492         /* Take the passed information and test it for minimum criteria */
493         /* Minimum password length */
494         if (strlen(newpass) < lp_min_passwd_length()) {
495                 /* too short, must be at least MINPASSWDLENGTH */
496                 DEBUG(0, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
497                        name, lp_min_passwd_length()));
498                 return (False); /* inform the user */
499         }
500
501         /* Password is same as old password */
502         if (strcmp(oldpass, newpass) == 0) {
503                 /* don't allow same password */
504                 DEBUG(2, ("Password Change: %s, New password is same as old\n", name)); /* log the attempt */
505                 return (False); /* inform the user */
506         }
507
508         /* 
509          * Check the old and new passwords don't contain any control
510          * characters.
511          */
512
513         len = strlen(oldpass);
514         for (i = 0; i < len; i++) {
515                 if (iscntrl((int)oldpass[i])) {
516                         DEBUG(0,
517                               ("chat_with_program: oldpass contains control characters (disallowed).\n"));
518                         return False;
519                 }
520         }
521
522         len = strlen(newpass);
523         for (i = 0; i < len; i++) {
524                 if (iscntrl((int)newpass[i])) {
525                         DEBUG(0,
526                               ("chat_with_program: newpass contains control characters (disallowed).\n"));
527                         return False;
528                 }
529         }
530         
531         pass = Get_Pwnam(name);
532
533 #ifdef WITH_PAM
534         if (lp_pam_password_change()) {
535                 BOOL ret;
536
537                 if (as_root)
538                         become_root();
539
540                 if (pass) {
541                         ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
542                 } else {
543                         ret = smb_pam_passchange(name, oldpass, newpass);
544                 }
545                         
546                 if (as_root)
547                         unbecome_root();
548
549                 return ret;
550         }
551 #endif
552
553         /* A non-PAM password change just doen't make sense without a valid local user */
554
555         if (pass == NULL)
556         {
557                 DEBUG(0,
558                       ("chgpasswd: user %s doesn't exist in the UNIX password database.\n",
559                        name));
560                 return False;
561         }
562
563         pstrcpy(passwordprogram, lp_passwd_program());
564         pstrcpy(chatsequence, lp_passwd_chat());
565
566         if (!*chatsequence) {
567                 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
568                 return (False);
569         }
570
571         if (!*passwordprogram) {
572                 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
573                 return (False);
574         }
575
576         if (as_root) {
577                 /* The password program *must* contain the user name to work. Fail if not. */
578                 if (strstr(passwordprogram, "%u") == NULL) {
579                         DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
580 the string %%u, and the given string %s does not.\n", passwordprogram ));
581                         return False;
582                 }
583         }
584
585         pstring_sub(passwordprogram, "%u", name);
586         /* note that we do NOT substitute the %o and %n in the password program
587            as this would open up a security hole where the user could use
588            a new password containing shell escape characters */
589
590         pstring_sub(chatsequence, "%u", name);
591         all_string_sub(chatsequence, "%o", oldpass, sizeof(pstring));
592         all_string_sub(chatsequence, "%n", newpass, sizeof(pstring));
593         return (chat_with_program
594                 (passwordprogram, pass, chatsequence, as_root));
595 }
596
597 #else /* ALLOW_CHANGE_PASSWORD */
598
599 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
600 {
601         DEBUG(0, ("Password changing not compiled in (user=%s)\n", name));
602         return (False);
603 }
604 #endif /* ALLOW_CHANGE_PASSWORD */
605
606 /***********************************************************
607  Code to check the lanman hashed password.
608 ************************************************************/
609
610 BOOL check_lanman_password(char *user, uchar * pass1,
611                            uchar * pass2, SAM_ACCOUNT **hnd)
612 {
613         uchar unenc_new_pw[16];
614         uchar unenc_old_pw[16];
615         SAM_ACCOUNT *sampass = NULL;
616         uint16 acct_ctrl;
617         const uint8 *lanman_pw;
618         BOOL ret;
619         
620         become_root();
621         ret = pdb_getsampwnam(sampass, user);
622         unbecome_root();
623
624         if (ret == False) {
625                 DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n"));
626                 pdb_free_sam(&sampass);
627                 return False;
628         }
629         
630         acct_ctrl = pdb_get_acct_ctrl     (sampass);
631         lanman_pw = pdb_get_lanman_passwd (sampass);
632
633         if (acct_ctrl & ACB_DISABLED) {
634                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
635                 pdb_free_sam(&sampass);
636                 return False;
637         }
638
639         if (lanman_pw == NULL) {
640                 if (acct_ctrl & ACB_PWNOTREQ) {
641                         /* this saves the pointer for the caller */
642                         *hnd = sampass;
643                         return True;
644                 } else {
645                         DEBUG(0, ("check_lanman_password: no lanman password !\n"));
646                         pdb_free_sam(&sampass);
647                         return False;
648                 }
649         }
650
651         /* Get the new lanman hash. */
652         D_P16(lanman_pw, pass2, unenc_new_pw);
653
654         /* Use this to get the old lanman hash. */
655         D_P16(unenc_new_pw, pass1, unenc_old_pw);
656
657         /* Check that the two old passwords match. */
658         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
659                 DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
660                 pdb_free_sam(&sampass);
661                 return False;
662         }
663
664         /* this saves the pointer for the caller */
665         *hnd = sampass;
666         return True;
667 }
668
669 /***********************************************************
670  Code to change the lanman hashed password.
671  It nulls out the NT hashed password as it will
672  no longer be valid.
673 ************************************************************/
674
675 BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2)
676 {
677         static uchar null_pw[16];
678         uchar unenc_new_pw[16];
679         BOOL ret;
680         uint16 acct_ctrl;
681         const uint8 *pwd;
682
683         if (sampass == NULL) {
684                 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
685                 return False;
686         }
687         
688         acct_ctrl = pdb_get_acct_ctrl(sampass);
689         pwd = pdb_get_lanman_passwd(sampass);
690
691         if (acct_ctrl & ACB_DISABLED) {
692                 DEBUG(0,("change_lanman_password: account %s disabled.\n",
693                        pdb_get_username(sampass)));
694                 return False;
695         }
696
697         if (pwd == NULL) { 
698                 if (acct_ctrl & ACB_PWNOTREQ) {
699                         uchar no_pw[14];
700                         memset(no_pw, '\0', 14);
701                         E_P16(no_pw, null_pw);
702
703                         /* Get the new lanman hash. */
704                         D_P16(null_pw, pass2, unenc_new_pw);
705                 } else {
706                         DEBUG(0,("change_lanman_password: no lanman password !\n"));
707                         return False;
708                 }
709         } else {
710                 /* Get the new lanman hash. */
711                 D_P16(pwd, pass2, unenc_new_pw);
712         }
713
714         if (!pdb_set_lanman_passwd(sampass, unenc_new_pw, PDB_CHANGED)) {
715                 return False;
716         }
717
718         if (!pdb_set_nt_passwd    (sampass, NULL, PDB_CHANGED)) {
719                 return False;   /* We lose the NT hash. Sorry. */
720         }
721
722         if (!pdb_set_pass_changed_now  (sampass)) {
723                 pdb_free_sam(&sampass);
724                 /* Not quite sure what this one qualifies as, but this will do */
725                 return False; 
726         }
727  
728         /* Now flush the sam_passwd struct to persistent storage */
729         become_root();
730         ret = pdb_update_sam_account (sampass);
731         unbecome_root();
732
733         return ret;
734 }
735
736 /***********************************************************
737  Code to check and change the OEM hashed password.
738 ************************************************************/
739 NTSTATUS pass_oem_change(char *user,
740                          uchar * lmdata, uchar * lmhash,
741                          uchar * ntdata, uchar * nthash)
742 {
743         fstring new_passwd;
744         const char *unix_user;
745         SAM_ACCOUNT *sampass = NULL;
746         NTSTATUS nt_status 
747                 = check_oem_password(user, lmdata, lmhash, ntdata, nthash,
748                                      &sampass, new_passwd, sizeof(new_passwd));
749
750         if (!NT_STATUS_IS_OK(nt_status))
751                 return nt_status;
752
753         /* 
754          * At this point we have the new case-sensitive plaintext
755          * password in the fstring new_passwd. If we wanted to synchronise
756          * with UNIX passwords we would call a UNIX password changing 
757          * function here. However it would have to be done as root
758          * as the plaintext of the old users password is not 
759          * available. JRA.
760          */
761
762         unix_user = pdb_get_username(sampass);
763
764         nt_status = change_oem_password(sampass, NULL, new_passwd);
765
766         memset(new_passwd, 0, sizeof(new_passwd));
767
768         pdb_free_sam(&sampass);
769
770         return nt_status;
771 }
772
773 /***********************************************************
774  Code to check the OEM hashed password.
775
776  this function ignores the 516 byte nt OEM hashed password
777  but does use the lm OEM password to check the nt hashed-hash.
778
779 ************************************************************/
780
781 static NTSTATUS check_oem_password(const char *user,
782                                uchar * lmdata, const uchar * lmhash,
783                                const uchar * ntdata, const uchar * nthash,
784                                SAM_ACCOUNT **hnd, char *new_passwd,
785                                int new_passwd_size)
786 {
787         static uchar null_pw[16];
788         static uchar null_ntpw[16];
789         SAM_ACCOUNT *sampass = NULL;
790         const uint8 *lanman_pw, *nt_pw;
791         uint16 acct_ctrl;
792         int new_pw_len;
793         uchar new_ntp16[16];
794         uchar unenc_old_ntpw[16];
795         uchar new_p16[16];
796         uchar unenc_old_pw[16];
797         char no_pw[2];
798         BOOL ret;
799
800         BOOL nt_pass_set = (ntdata != NULL && nthash != NULL);
801
802         *hnd = NULL;
803
804         pdb_init_sam(&sampass);
805
806         become_root();
807         ret = pdb_getsampwnam(sampass, user);
808         unbecome_root();
809
810         if (ret == False) {
811                 DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
812                 pdb_free_sam(&sampass);
813                 return NT_STATUS_WRONG_PASSWORD;
814                 /*
815                   TODO: check what Win2k returns for this:
816                   return NT_STATUS_NO_SUCH_USER; 
817                 */
818         }
819
820         acct_ctrl = pdb_get_acct_ctrl(sampass);
821         
822         if (acct_ctrl & ACB_DISABLED) {
823                 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
824                 pdb_free_sam(&sampass);
825                 return NT_STATUS_ACCOUNT_DISABLED;
826         }
827
828         /* construct a null password (in case one is needed */
829         no_pw[0] = 0;
830         no_pw[1] = 0;
831         nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
832
833         /* save pointers to passwords so we don't have to keep looking them up */
834         lanman_pw = pdb_get_lanman_passwd(sampass);
835         nt_pw = pdb_get_nt_passwd(sampass);
836
837         /* check for null passwords */
838         if (lanman_pw == NULL) {
839                 if (!(acct_ctrl & ACB_PWNOTREQ)) {
840                         DEBUG(0,("check_oem_password: no lanman password !\n"));
841                         pdb_free_sam(&sampass);
842                         return NT_STATUS_WRONG_PASSWORD;
843                 }
844         }
845         
846         if (pdb_get_nt_passwd(sampass) == NULL && nt_pass_set) {
847                 if (!(acct_ctrl & ACB_PWNOTREQ)) {
848                         DEBUG(0,("check_oem_password: no ntlm password !\n"));
849                         pdb_free_sam(&sampass);
850                         return NT_STATUS_WRONG_PASSWORD;
851                 }
852         }
853         
854         /* 
855          * Call the hash function to get the new password.
856          */
857         SamOEMhash( lmdata, lanman_pw, 516);
858
859         /* 
860          * The length of the new password is in the last 4 bytes of
861          * the data buffer.
862          */
863
864         new_pw_len = IVAL(lmdata, 512);
865         if (new_pw_len < 0 || new_pw_len > new_passwd_size - 1) {
866                 DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len));
867                 pdb_free_sam(&sampass);
868                 return NT_STATUS_WRONG_PASSWORD;
869         }
870
871         if (nt_pass_set) {
872                 /*
873                  * nt passwords are in unicode
874                  */
875                 pull_ucs2(NULL, new_passwd, 
876                           (const smb_ucs2_t *)&lmdata[512 - new_pw_len],
877                           new_passwd_size, new_pw_len, 0);
878         } else {
879                 memcpy(new_passwd, &lmdata[512 - new_pw_len], new_pw_len);
880                 new_passwd[new_pw_len] = 0;
881         }
882
883         /*
884          * To ensure we got the correct new password, hash it and
885          * use it as a key to test the passed old password.
886          */
887
888         nt_lm_owf_gen(new_passwd, new_ntp16, new_p16);
889
890         if (!nt_pass_set) {
891                 /*
892                  * Now use new_p16 as the key to see if the old
893                  * password matches.
894                  */
895                 D_P16(new_p16, lmhash, unenc_old_pw);
896
897                 if (memcmp(lanman_pw, unenc_old_pw, 16)) {
898                         DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
899                         pdb_free_sam(&sampass);
900                         return NT_STATUS_WRONG_PASSWORD;
901                 }
902
903 #ifdef DEBUG_PASSWORD
904                 DEBUG(100,
905                       ("check_oem_password: password %s ok\n", new_passwd));
906 #endif
907                 *hnd = sampass;
908                 return NT_STATUS_OK;
909         }
910
911         /*
912          * Now use new_p16 as the key to see if the old
913          * password matches.
914          */
915         D_P16(new_ntp16, lmhash, unenc_old_pw);
916         D_P16(new_ntp16, nthash, unenc_old_ntpw);
917
918         if (memcmp(lanman_pw, unenc_old_pw, 16)) {
919                 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
920                 pdb_free_sam(&sampass);
921                 return NT_STATUS_WRONG_PASSWORD;
922         }
923
924         if (memcmp(nt_pw, unenc_old_ntpw, 16)) {
925                 DEBUG(0,("check_oem_password: old nt password doesn't match.\n"));
926                 pdb_free_sam(&sampass);
927                 return NT_STATUS_WRONG_PASSWORD;
928         }
929 #ifdef DEBUG_PASSWORD
930         DEBUG(100, ("check_oem_password: password %s ok\n", new_passwd));
931 #endif
932
933         *hnd = sampass;
934         return NT_STATUS_OK;
935 }
936
937 /***********************************************************
938  Code to change the oem password. Changes both the lanman
939  and NT hashes.  Old_passwd is almost always NULL.
940 ************************************************************/
941
942 NTSTATUS change_oem_password(SAM_ACCOUNT *hnd, char *old_passwd, char *new_passwd)
943 {
944         BOOL ret;
945         uint32 min_len;
946
947         if (time(NULL) < pdb_get_pass_can_change_time(hnd)) {
948                 DEBUG(1, ("user %s cannot change password now, must wait until %s\n", 
949                           pdb_get_username(hnd), http_timestring(pdb_get_pass_can_change_time(hnd))));
950                 return NT_STATUS_PASSWORD_RESTRICTION;
951         }
952
953         if (account_policy_get(AP_MIN_PASSWORD_LEN, &min_len) && (strlen(new_passwd) < min_len)) {
954                 DEBUG(1, ("user %s cannot change password - password too short\n", 
955                           pdb_get_username(hnd)));
956                 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
957                 return NT_STATUS_PASSWORD_RESTRICTION;
958 /*              return NT_STATUS_PWD_TOO_SHORT; */
959         }
960
961         /* Take the passed information and test it for minimum criteria */
962         /* Minimum password length */
963         if (strlen(new_passwd) < lp_min_passwd_length()) {
964                 /* too short, must be at least MINPASSWDLENGTH */
965                 DEBUG(1, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
966                        pdb_get_username(hnd), lp_min_passwd_length()));
967                 return NT_STATUS_PASSWORD_RESTRICTION;
968 /*              return NT_STATUS_PWD_TOO_SHORT; */
969         }
970
971         /* TODO:  Add cracklib support here */
972
973         /*
974          * If unix password sync was requested, attempt to change
975          * the /etc/passwd database first. Return failure if this cannot
976          * be done.
977          *
978          * This occurs before the oem change, becouse we don't want to
979          * update it if chgpasswd failed.
980          *
981          * Conditional on lp_unix_password_sync() becouse we don't want
982          * to touch the unix db unless we have admin permission.
983          */
984         
985         if(lp_unix_password_sync() && IS_SAM_UNIX_USER(hnd) 
986            && !chgpasswd(pdb_get_username(hnd),
987                          old_passwd, new_passwd, False)) {
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         become_root();
997         ret = pdb_update_sam_account (hnd);
998         unbecome_root();
999
1000         if (!ret) {
1001                 return NT_STATUS_ACCESS_DENIED;
1002         }
1003         
1004         return NT_STATUS_OK;
1005 }