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