/* pam_winbind module

   Copyright Andrew Tridgell <tridge@samba.org> 2000

   largely based on pam_userdb by Christian Gafton <gafton@redhat.com> 
*/

#include "pam_winbind.h"

/* prototypes from common.c */
void init_request(struct winbindd_request *req,int rq_type);
int write_sock(void *buffer, int count);
int read_reply(struct winbindd_response *response);

/* some syslogging */
static void _pam_log(int err, const char *format, ...)
{
	va_list args;

	va_start(args, format);
	openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
	vsyslog(err, format, args);
	va_end(args);
	closelog();
}

static int ctrl	 = 0;

static int _pam_parse(int argc, const char **argv)
{
     /* step through arguments */
     for (ctrl = 0; argc-- > 0; ++argv) {

          /* generic options */

          if (!strcmp(*argv,"debug"))
               ctrl |= PAM_DEBUG_ARG;
	  else if (!strcasecmp(*argv, "use_authtok"))
	      ctrl |= PAM_USE_AUTHTOK_ARG;
	  else if (!strcasecmp(*argv, "unknown_ok"))
	      ctrl |= PAM_UNKNOWN_OK_ARG;
	  else {
               _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv);
          }
     }

     return ctrl;
}

static int winbind_request(enum winbindd_cmd req_type,
                           struct winbindd_request *request,
                           struct winbindd_response *response)
{
	/* Fill in request and send down pipe */
	init_request(request, req_type);
	
	if (write_sock(request, sizeof(*request)) == -1) {
		return -2;
	}
	
	/* Wait for reply */
	if (read_reply(response) == -1) {
		return -2;
	}

	/* Copy reply data from socket */
	if (response->result != WINBINDD_OK) {
		return 1;
	}
	
	return 0;
}

/* talk to winbindd */
static int winbind_auth_request(const char *user, const char *pass)
{
	struct winbindd_request request;
	struct winbindd_response response;

	ZERO_STRUCT(request);

	strncpy(request.data.auth.user, user, 
                sizeof(request.data.auth.user)-1);

	strncpy(request.data.auth.pass, pass, 
                sizeof(request.data.auth.pass)-1);
	
        return winbind_request(WINBINDD_PAM_AUTH, &request, &response);
}

/* talk to winbindd */
static int winbind_chauthtok_request(const char *user, const char *oldpass,
                                     const char *newpass)
{
	struct winbindd_request request;
	struct winbindd_response response;

	ZERO_STRUCT(request);

        if (request.data.chauthtok.user == NULL) return -2;

	strncpy(request.data.chauthtok.user, user, 
                sizeof(request.data.chauthtok.user) - 1);

        if (oldpass != NULL) {
            strncpy(request.data.chauthtok.oldpass, oldpass, 
                    sizeof(request.data.chauthtok.oldpass) - 1);
        } else {
            request.data.chauthtok.oldpass[0] = '\0';
        }
	
        if (newpass != NULL) {
            strncpy(request.data.chauthtok.newpass, newpass, 
                    sizeof(request.data.chauthtok.newpass) - 1);
        } else {
            request.data.chauthtok.newpass[0] = '\0';
        }
	
        return winbind_request(WINBINDD_PAM_CHAUTHTOK, &request, &response);
}

/*
 * Looks up an user name and checks the password
 *
 * return values:
 *	 1  = User not found
 *	 0  = OK
 * 	-1  = Password incorrect
 *	-2  = System error
 */
static int user_lookup(const char *user, const char *pass)
{
	return winbind_auth_request(user, pass);
}

/*
 * Checks if a user has an account
 *
 * return values:
 *	 1  = User not found
 *	 0  = OK
 * 	-1  = System error
 */
static int valid_user(const char *user)
{
	if (getpwnam(user)) return 0;
	return 1;
}

/* --- authentication management functions --- */

/* Attempt a conversation */

static int converse(pam_handle_t *pamh, int nargs,
		    struct pam_message **message,
		    struct pam_response **response)
{
    int retval;
    struct pam_conv *conv;

    retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ;
    if (retval == PAM_SUCCESS) {
	retval = conv->conv(nargs, (const struct pam_message **)message,
			    response, conv->appdata_ptr);
    }
	
    return retval; /* propagate error status */
}


static char *_pam_delete(register char *xx)
{
    _pam_overwrite(xx);
    _pam_drop(xx);
    return NULL;
}

/*
 * This is a conversation function to obtain the user's password
 */
static int auth_conversation(pam_handle_t *pamh)
{
    struct pam_message msg, *pmsg;
    struct pam_response *resp;
    int retval;
    char * token = NULL;
    
    pmsg = &msg;
    msg.msg_style = PAM_PROMPT_ECHO_OFF;
    msg.msg = "Password: ";

    /* so call the conversation expecting i responses */
    resp = NULL;
    retval = converse(pamh, 1, &pmsg, &resp);

    if (resp != NULL) {
	char * const item;
	/* interpret the response */
	if (retval == PAM_SUCCESS) {     /* a good conversation */
	    token = x_strdup(resp[0].resp);
	    if (token == NULL) {
		return PAM_AUTHTOK_RECOVER_ERR;
	    }
	}

	/* set the auth token */
	retval = pam_set_item(pamh, PAM_AUTHTOK, token);
	token = _pam_delete(token);   /* clean it up */
	if ( (retval != PAM_SUCCESS) ||
	     (retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &item)) != PAM_SUCCESS ) {
	    return retval;
	}
	
	_pam_drop_reply(resp, 1);
    } else {
	retval = (retval == PAM_SUCCESS)
	    ? PAM_AUTHTOK_RECOVER_ERR:retval ;
    }

    return retval;
}

PAM_EXTERN
int pam_sm_authenticate(pam_handle_t *pamh, int flags,
			int argc, const char **argv)
{
     const char *username;
     const char *password;
     int retval = PAM_AUTH_ERR;
    
     /* parse arguments */
     ctrl = _pam_parse(argc, argv);

     /* Get the username */
     retval = pam_get_user(pamh, &username, NULL);
     if ((retval != PAM_SUCCESS) || (!username)) {
        if (ctrl & PAM_DEBUG_ARG)
            _pam_log(LOG_DEBUG,"can not get the username");
        return PAM_SERVICE_ERR;
     }
     
     if ((ctrl & PAM_USE_AUTHTOK_ARG) == 0) {
	 /* Converse just to be sure we have the password */
	 retval = auth_conversation(pamh);
	 if (retval != PAM_SUCCESS) {
	     _pam_log(LOG_ERR, "could not obtain password for `%s'",
		      username);
	     return PAM_CONV_ERR;
	 }
     }
     
     /* Get the password */
     retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &password);
     if (retval != PAM_SUCCESS) {
	 _pam_log(LOG_ERR, "Could not retrive user's password");
	 return PAM_AUTHTOK_ERR;
     }
     
     if (ctrl & PAM_DEBUG_ARG)
	 _pam_log(LOG_INFO, "Verify user `%s' with password `%s'",
		  username, password);
     
     /* Now use the username to look up password */
     retval = user_lookup(username, password);
     switch (retval) {
	 case -2:
	     /* some sort of system error. The log was already printed */
	     return PAM_SERVICE_ERR;    
	 case -1:
	     /* incorrect password */
	     _pam_log(LOG_WARNING, "user `%s' denied access (incorrect password)", username);
	     return PAM_AUTH_ERR;
	 case 1:
		 /* the user does not exist */
	     if (ctrl & PAM_DEBUG_ARG)
		 _pam_log(LOG_NOTICE, "user `%s' not found",
			  username);
	     if (ctrl & PAM_UNKNOWN_OK_ARG) {
		 return PAM_IGNORE;
	     }	 
	     return PAM_USER_UNKNOWN;
	 case 0:
	     /* Otherwise, the authentication looked good */
	     _pam_log(LOG_NOTICE, "user '%s' granted access", username);
	     return PAM_SUCCESS;
	 default:
	     /* we don't know anything about this return value */
	     _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'",
		      retval, username);
	     return PAM_SERVICE_ERR;
     }
     /* should not be reached */
     return PAM_IGNORE;
}

PAM_EXTERN
int pam_sm_setcred(pam_handle_t *pamh, int flags,
		   int argc, const char **argv)
{
    return PAM_SUCCESS;
}

/*
 * Account management. We want to verify that the account exists 
 * before returning PAM_SUCCESS
 */
PAM_EXTERN
int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
		   int argc, const char **argv)
{
    const char *username;
    int retval = PAM_USER_UNKNOWN;

    /* parse arguments */
    ctrl = _pam_parse(argc, argv);

    /* Get the username */
    retval = pam_get_user(pamh, &username, NULL);
    if ((retval != PAM_SUCCESS) || (!username)) {
	if (ctrl & PAM_DEBUG_ARG)
	    _pam_log(LOG_DEBUG,"can not get the username");
	return PAM_SERVICE_ERR;
    }

    /* Verify the username */
    retval = valid_user(username);
    switch (retval) {
	case -1:
	    /* some sort of system error. The log was already printed */
	    return PAM_SERVICE_ERR;
	case 1:
	    /* the user does not exist */
	    if (ctrl & PAM_DEBUG_ARG)
		_pam_log(LOG_NOTICE, "user `%s' not found",
			 username);
	    if (ctrl & PAM_UNKNOWN_OK_ARG)
		return PAM_IGNORE;
	    return PAM_USER_UNKNOWN;
	case 0:
	    /* Otherwise, the authentication looked good */
	    _pam_log(LOG_NOTICE, "user '%s' granted access", username);
	    return PAM_SUCCESS;
	default:
	    /* we don't know anything about this return value */
	    _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'",
		     retval, username);
	    return PAM_SERVICE_ERR;
    }
    
    /* should not be reached */
    return PAM_IGNORE;
}


PAM_EXTERN
int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, 
                     const char **argv)
{
    int retval;
    char *newpw, *oldpw;
    const char *user;

    /* Get name of a user */

    retval = pam_get_user(pamh, &user, "Username: ");

    if (retval != PAM_SUCCESS) {
        return retval;
    }

    /* XXX check in domain format */

    /* Perform preliminary check and store requested password for updating
       later on */

    if (flags & PAM_PRELIM_CHECK) {
        struct pam_message msg[3], *pmsg[3];
        struct pam_response *resp;

        /* Converse to ensure we have the current password */

        retval = auth_conversation(pamh);

        if (retval != PAM_SUCCESS) {
            return retval;
        }

        /* Obtain and verify current password */

        pmsg[0] = &msg[0];
        msg[0].msg_style = PAM_TEXT_INFO;
        msg[0].msg = "Changing password for user %s";

        pmsg[1] = &msg[1];
        msg[1].msg_style = PAM_PROMPT_ECHO_OFF;
        msg[1].msg = "New NT password: ";

        pmsg[2] = &msg[2];
        msg[2].msg_style = PAM_PROMPT_ECHO_OFF;
        msg[2].msg = "Retype new NT password: ";

        resp = NULL;

        retval = converse(pamh, 3, pmsg, &resp);

        if (resp != NULL) {

            if (retval == PAM_SUCCESS) {

                /* Check password entered correctly */

                if (strcmp(resp[1].resp, resp[2].resp) != 0) { 
                    struct pam_response *resp2;

                    msg[0].msg_style = PAM_ERROR_MSG;
                    msg[0].msg = "Sorry, passwords do not match";

                    converse(pamh, 1, pmsg, &resp2);

                    _pam_drop_reply(resp, 3);
                    _pam_drop_reply(resp2, 1);

                    return PAM_AUTHTOK_RECOVER_ERR;
                }

                /* Store passwords */

                retval = pam_set_item(pamh, PAM_OLDAUTHTOK, resp[1].resp);
                _pam_drop_reply(resp, 3);
            }
        }

        /* XXX What happens if root? */
        /* XXX try first pass and use first pass args */

        return retval;
    }

    if (flags & PAM_UPDATE_AUTHTOK) {

        retval = pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **)&newpw);
        if (retval != PAM_SUCCESS) {
            return PAM_AUTHTOK_ERR;
        }

        retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&oldpw);
        if (retval != PAM_SUCCESS) {
            return PAM_AUTHTOK_ERR;
        }

        fprintf(stderr, "oldpw = %s, newpw = %s\n", oldpw, newpw);

        if (retval == PAM_SUCCESS && 
            winbind_chauthtok_request(user, oldpw, newpw) == 0) {
            return PAM_SUCCESS;
        }

        return PAM_AUTHTOK_ERR;
    }

    return PAM_SERVICE_ERR;
}

/* HP added session open and close handlers to always return success */
PAM_EXTERN
int pam_sm_open_session(pam_handle_t *pamh, int flags,
		int argc, const char **argv)
{
	/* parse arguments */
	ctrl = _pam_parse(argc, argv);
	if (ctrl & PAM_DEBUG_ARG)
		_pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_open_session handler") ;
	return PAM_SUCCESS;
}

PAM_EXTERN
int pam_sm_close_session(pam_handle_t *pamh, int flags,
		int argc, const char **argv)
{
	/* parse arguments */
	ctrl = _pam_parse(argc, argv);
	if (ctrl & PAM_DEBUG_ARG)
		_pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_close_session handler");
	return PAM_SUCCESS;
}
/* end HP add */

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_winbind_modstruct = {
     MODULE_NAME,
     pam_sm_authenticate,
     pam_sm_setcred,
     pam_sm_acct_mgmt,
     pam_sm_open_session,
     pam_sm_close_session,
     pam_sm_chauthtok
};

#endif

/*
 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
 * Copyright (c) Tim Potter      <tpot@samba.org>   2000
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * ALTERNATIVELY, this product may be distributed under the terms of
 * the GNU Public License, in which case the provisions of the GPL are
 * required INSTEAD OF the above restrictions.  (This clause is
 * necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 *
 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
