Hi, I first want to thank *everyone* who participated in the previous thread and when needed, took the time to add their valuable comments.
I attached "password-quality.c" (it's just this part) -- I hope I got this right -- if not let me know what to change and I'll do it. At the end of the file, there are functions that could be move in other files (.../source/lib/???). If you want to move anything, let me know what to move and the "destination" file. For the next few days, here's my TODO list prior to post a "release candidate" patch: - documentation: update smb.5.sgml - Doxygen comments - finish the simple external script I started (add change uid/gid code) - change DEBUG() code to appropriate log level - apply changes from your comments - create a patch againts HEAD (it's a start!). I'll do the 2_2 / 3_0 once it's in HEAD, well I hope we will add this feature in the 2_2? Question: Do we want the external script to return its version number? (Version: xyz\n")? If we ever expect a new field from the child -- it will log "bad communication". Should the PWQUAL_PROTOCOL_VERSION be general? We could move it later if we want? That's about it for now, I guess! Regards, Pierre B.
/* * TODO: * * Doxygen documentation * change DEBUG() code to appropriate log level * */ /* Unix SMB/CIFS implementation. Samba utility functions Password Quality: Help users not to choose a weak password. Copyright (C) Andrew Bartlett 2003 Copyright (C) Pierre Belanger 2003 ([EMAIL PROTECTED]) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "includes.h" /* Increment when making changes in the communication protocol */ #define PWQUAL_PROTOCOL_VERSION "1" static void gotalarm_sig(void); static uint32 ascii2hex(char ascii); static int ZEROxStr2uint32(char *strx, uint32 *hex32); static NTSTATUS password_quality_script(SAM_ACCOUNT *hnd, char *new_passwd); static BOOL strhasctrl(const char *str); static NTSTATUS pre_chk(const char *username,const char *fullname,char *new_pw); static int gotalarm; /*************************************************************** Signal function to tell us we timed out. ****************************************************************/ static void gotalarm_sig(void) { gotalarm = 1; } /****************************************************************** Main function to catch weak new passwords ******************************************************************/ NTSTATUS password_quality(SAM_ACCOUNT *hnd, char *new_password) { NTSTATUS ntstatusresult; ntstatusresult = password_quality_script(hnd, new_password); if (!NT_STATUS_IS_OK(ntstatusresult)) { DEBUG(0,("user %s could not change password NTSTATUS=0x%0.8x\n", pdb_get_username(hnd), ntstatusresult.v)); return ntstatusresult; } /* Add other supports here if needed */ DEBUG(0,("user %s changed password\n", pdb_get_username(hnd))); return(ntstatusresult); } /****************************************************************** Run the password quality script ******************************************************************/ static NTSTATUS password_quality_script(SAM_ACCOUNT *hnd, char *new_passwd) { int fd1[2], fd2[2]; char *cmdname; const char *username, *fullname; pid_t child_pid; NTSTATUS ntprerun; /* check if command is configured */ cmdname = lp_password_quality_script(); if (!cmdname || (*cmdname == '\0')) return NT_STATUS_OK; username = pdb_get_username(hnd); fullname = pdb_get_fullname(hnd); /* pre-run security check */ ntprerun = pre_chk(username, fullname, new_passwd); if (! NT_STATUS_EQUAL(ntprerun, NT_STATUS_OK)) { return ntprerun; } if (pipe(fd1) || pipe(fd2)) { DEBUG(0,("could not create pipes\n")); return NT_STATUS_ACCESS_DENIED; } CatchChildLeaveStatus(); child_pid = sys_fork(); if (child_pid < 0) { CatchChild(); close(fd1[0]); close(fd1[1]); close(fd2[0]); close(fd2[1]); DEBUG(0,("could not fork\n")); return NT_STATUS_ACCESS_DENIED; } if (child_pid > 0) { /* * Parent. */ int fd_in, fd_out, done, child_status; pid_t wpid; ssize_t length; char *c, *buf, *reservedword, *msg; pstring pbuf, pntstatus, presult; NTSTATUS ntstatusmap; void (*oldsighandler)(int); close(fd1[0]); close(fd2[1]); fd_out = fd1[1]; fd_in = fd2[0]; asprintf (&buf, "Version: %s\nUsername: %s\nFullName: %s\n" "Password: %s\n.\n", PWQUAL_PROTOCOL_VERSION, username, fullname, new_passwd); if ((length = sys_write(fd_out, buf, strlen(buf))) < 0 ) { close(fd_out); close(fd_in); SAFE_FREE(buf); DEBUG(0,("sys_write returned %s\n", strerror(errno))); kill(child_pid, SIGKILL); return NT_STATUS_ACCESS_DENIED; } SAFE_FREE(buf); /* don't hang in read() due to a broken program */ oldsighandler=CatchSignal(SIGALRM,SIGNAL_CAST gotalarm_sig); alarm(15); /* cli->timeout set to 20000ms */ gotalarm = 0; while((length = read(fd_in, &pbuf[0], PSTRING_LEN - 1)) < 0) { if (gotalarm == 1) { kill(child_pid, SIGTERM); DEBUG(0,("read timeout waiting for child\n")); break; } if ((length == -1) && (errno == EINTR)) { errno = 0; continue; } DEBUG(0,("read error %s\n", strerror(errno))); break; } close(fd_out); close(fd_in); /* get child exit status */ alarm(2); gotalarm = 0; while((wpid = sys_waitpid(child_pid, &child_status, 0)) < 0) { if (gotalarm == 1) { DEBUG(0,("exit status timeout\n")); kill(child_pid, SIGKILL); gotalarm = 0; /* avoid loop */ continue; } if(errno == EINTR) { errno = 0; continue; } DEBUG(0,("could not get child exit status\n")); break; /* sys_waitpid will exit with error */ } alarm(0); CatchSignal(SIGALRM, SIGNAL_CAST oldsighandler); CatchChild(); if (length < 0 || wpid < 0) { return NT_STATUS_ACCESS_DENIED; } /* check child exit status */ if (!NT_STATUS_IS_OK(map_nt_error_from_unix(child_status))) { DEBUG(1,("child error exit(%d) != 0\n", child_status)); } if (length == 0) { DEBUG(1,("child returned nothing - read length = 0\n")); return NT_STATUS_ACCESS_DENIED; } /* Parse response from external program */ pbuf[length] = '\0'; presult[0] = '\0';pntstatus[0] = '\0'; done = 0; for (c = &pbuf[0]; *c != '\0'; c++) { if (!strcmp(c, ".\n")) { done = 1; break; } reservedword = c; if ((c = strpbrk(c, " :")) == (char *)NULL) { DEBUG(1,("received bad response %s\n", reservedword)); return NT_STATUS_ACCESS_DENIED; } *c = '\0'; /* end of reserved word */ c++; while (((*c==' ')||(*c==':'))&&(*c!='\0')) c++; msg = c; if ((c = strpbrk(c, "\n")) == (char *)NULL) { DEBUG(1,("received bad response %s: %s!\n", reservedword, msg)); return NT_STATUS_ACCESS_DENIED; } *c = '\0'; /* end of message */ if (!StrCaseCmp(reservedword, "ntstatus")) { pstrcpy(pntstatus, msg); DEBUG(3,("got %s: %s\n", reservedword, msg)); continue; } if (!StrCaseCmp(reservedword, "result")) { pstrcpy(presult, msg); DEBUG(3,("got %s: %s\n", reservedword, msg)); continue; } DEBUG(1,("unsupported %s: %s\n", reservedword, msg)); break; /* child is broken or not up to date, break */ } /* parse end */ if (!done || (pntstatus[0]=='\0') || (presult[0]=='\0')) { DEBUG(1,("bad communication with child\n")); return NT_STATUS_ACCESS_DENIED; } DEBUG(1,("got NTStatus: %s Result: %s\n", pntstatus, prresult)); if (!StrnCaseCmp(pntstatus, "0x", 2)) { /* * pntstatus is 0x... hexstring format */ uint32 nthex; if (ZEROxStr2uint32(pntstatus, &nthex)) { DEBUG(1,("received bad hex '%s'\n", pntstatus)); return NT_STATUS_ACCESS_DENIED; } ntstatusmap = NT_STATUS(nthex); } else { /* * pntstatus must be an NTSTATUS string */ ntstatusmap = nt_status_string_to_code(pntstatus); if NT_STATUS_EQUAL(ntstatusmap,NT_STATUS_UNSUCCESSFUL) { /* if there is no match, deny changes */ DEBUG(1,("received undefined NTSTATUS %s\n", pntstatus)); return NT_STATUS_ACCESS_DENIED; } } /* return received the hex value or a matched NTSTATUS string */ return ntstatusmap; } else { /* * Child. */ int fd_null; CatchChild(); close(fd1[1]); close(fd2[0]); if (sys_dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO) { DEBUG(0,("could not redirect stdin\n")); exit(83); } if (sys_dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) { DEBUG(0,("could not redirect stdout\n")); exit(84); } fd_null = open("/dev/null", O_WRONLY); if (fd_null >= 0 ) { if (sys_dup2(fd_null, STDERR_FILENO) != STDERR_FILENO) { DEBUG(0,("could not redirect stderr\n")); exit(85); } close(fd_null); } gain_root_privilege(); gain_root_group_privilege(); execl("/bin/sh", "sh", "-c", cmdname, NULL); /* not reached */ exit(86); return NT_STATUS_ACCESS_DENIED; } return NT_STATUS_ACCESS_DENIED; } /*************************************************************** Pre security check ****************************************************************/ static NTSTATUS pre_chk(const char *username, const char *fullname, char *new_password) { /* avoid injection attacks */ if (strhasctrl(new_password)) { DEBUG(0,("new password contains ctrl char\n")); return NT_STATUS_ILL_FORMED_PASSWORD; } if (strhasctrl(username)) { DEBUG(0,("username contains ctrl char\n")); return NT_STATUS_ACCESS_DENIED; } if (strhasctrl(fullname)) { DEBUG(0,("fullname contains ctrl char\n")); return NT_STATUS_ACCESS_DENIED; } return NT_STATUS_OK; } /*************************************************************** Returns the hex value from a char ****************************************************************/ static uint32 ascii2hex(char ascii) { return( (ascii <= '9') ? (uint32)(ascii - '0') : (uint32)(ascii - ('A' - 0x0A))); } /*************************************************************** Convert a "0x 32bits string" (0x87654321) to uint32 ****************************************************************/ static int ZEROxStr2uint32(char *strx, uint32 *hex32) { char *onehex; if (!strx) return(-1); if (strlen(strx) > 10 || StrnCaseCmp(strx, "0x", 2)) { return(-1); } *hex32 = 0x00000000; for(onehex = &strx[2]; *onehex != '\0'; onehex++) { *onehex = toupper(*onehex); if (!((*onehex >= '0') && (*onehex <= '9') || (*onehex >= 'A') && (*onehex <= 'F'))) { return(-1); } *hex32 = (*hex32 << 4) | (uint32)ascii2hex(*onehex); } return(0); } /*************************************************************** Check if string contains any control characters ****************************************************************/ static BOOL strhasctrl(const char *str) { int i, len; len = strlen(str); for (i = 0; i < len; i++) { if (iscntrl((int)str[i])) { return True; } } return False; }