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;
}

Reply via email to