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