password quality script - pre-release
Pierre Belanger
pbelang1 at oss.cantel.rogers.com
Tue Feb 18 19:52:13 GMT 2003
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.
-------------- next part --------------
/*
* 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 (belanger at pobox.com)
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;
}
More information about the samba-technical
mailing list