/*
 * pam_authtok module
 */

/*
 * Written by Ron Peterson <ron.peterson@yellowbank.com> 2006
 * 
 * Based on cracklib codebase written by Cristian Gafton
 * <gafton@redhat.com> 1996/09/10
 *
 * This module pushes authorization tokens into a PostgreSQL database.
 * It does _not_ directly update any active authentication databases.
 * Instead, the PostgreSQL database is used as a repository from which
 * various authentication systems can be populated (e.g. Unix passwd
 * system, LDAP, Active Directory, etc.) using appropriate code.
 *
 */

#define TEST_FILE	    "/tmp/authtok.test"

#define TEST_DB_HOST	"localhost"
#define TEST_DB_DB  	"iddb"
#define TEST_DB_USER    "iddb"
#define TEST_DB_PASS    "wreckless"

// #include <security/_pam_aconf.h>

#include <stdio.h>
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>

#include "libpq-fe.h"

#define PROMPT1 "New %s%spassword: "
#define PROMPT2 "Retype new %s%spassword: "
#define MISTYPED_PASS "Sorry, passwords do not match"

/*
 * here, we make a definition for the externally accessible function
 * in this file (this definition is required for static a module
 * but strongly encouraged generally) it is used to instruct the
 * modules include file to define the function prototypes.
 */

#define PAM_SM_PASSWORD

#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <security/pam_appl.h>

/*
#ifndef LINUX_PAM 
#include <security/pam_appl.h>
#endif
*/

/* some syslogging */

static void _pam_log(int err, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    openlog("PAM-Authtok", LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}

/* argument parsing */
#define PAM_DEBUG_ARG       0x0001

struct authtok_options {
	int retry_times;
	int use_authtok;
	char prompt_type[BUFSIZ];
};

#define CO_RETRY_TIMES  1
#define CO_USE_AUTHTOK  0

static int _pam_parse(struct authtok_options *opt, int argc, const char **argv)
{
     int ctrl=0;

     /* step through arguments */
     for (ctrl=0; argc-- > 0; ++argv) {
	 char *ep = NULL;

	 /* generic options */

	 if (!strcmp(*argv,"debug"))
	     ctrl |= PAM_DEBUG_ARG;
	 else if (!strncmp(*argv,"type=",5))
	     strncpy(opt->prompt_type, *argv+5, sizeof(opt->prompt_type) - 1);
	 else if (!strncmp(*argv,"retry=",6)) {
	     opt->retry_times = strtol(*argv+6,&ep,10);
	     if (!ep || (opt->retry_times < 1))
		 opt->retry_times = CO_RETRY_TIMES;
	 } else if (!strncmp(*argv,"use_authtok",11)) {
		 opt->use_authtok = 1;
	 } else {
	     _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
	 }
     }
     opt->prompt_type[sizeof(opt->prompt_type) - 1] = '\0';

     return ctrl;
}

/* Helper functions */

/* this is a front-end for module-application conversations */
static int converse(pam_handle_t *pamh, int ctrl, 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);
        if (retval != PAM_SUCCESS && (ctrl && PAM_DEBUG_ARG)) {
            _pam_log(LOG_DEBUG, "conversation failure [%s]",
                                 pam_strerror(pamh, retval));
        }
    } else {
        _pam_log(LOG_ERR, "couldn't obtain coversation function [%s]",
                pam_strerror(pamh, retval));
    }

    return retval;                  /* propagate error status */
}

static int make_remark(pam_handle_t *pamh, unsigned int ctrl,
                       int type, const char *text)
{
    struct pam_message *pmsg[1], msg[1];
    struct pam_response *resp;
    int retval;

    pmsg[0] = &msg[0];
    msg[0].msg = text;
    msg[0].msg_style = type;
    resp = NULL;

    retval = converse(pamh, ctrl, 1, pmsg, &resp);
    if (retval == PAM_SUCCESS)
	_pam_drop_reply(resp, 1);

    return retval;
}

/* use this to free strings. ESPECIALLY password strings */
static char *_pam_delete(register char *xx)
{
    _pam_overwrite(xx);
    free(xx);
    return NULL;
}

static
void
exit_nicely(PGconn *conn)
{
   PQfinish(conn);
   exit(1);
}


/* this is where we actually do our stuff */
int save_password(const char* forwho, const char* oldpass, const char* newpass)
{
/*
   const char* conninfo;
   char*       buf;
   int         len;
   PGConn*     conn;
   PGResult*   res;

   PGconn *PQsetdbLogin(const char *pghost,
                        const char *pgport,
                        const char *pgoptions,
                        const char *pgtty,
                        const char *dbName,
                        const char *login,
                        const char *pwd);


   conn = PQsetdbLogin( "localhost",
                        "5432",
                        "",
                        "",
                        "iddb",
                        "forwho",
                        "oldpass" );


   switch(PQstatus(conn))
   {
      case CONNECTION_STARTED:
         feedback = "Connecting...";
         break;

      case CONNECTION_MADE:
         feedback = "Connected to server...";
         break;
.
.
.
            default:
               feedback = "Connecting...";
   }
*/

	FILE *afile;

	afile = fopen(TEST_FILE, "a");
	if (afile == NULL) {
       _pam_log(LOG_DEBUG, "PAM_authtok: failed to connect to database");
		return PAM_AUTHTOK_ERR;
    }

    fprintf( afile, "%s: %s -> %s\n", forwho, oldpass, newpass );

	fclose(afile);

    return PAM_SUCCESS;
}

/* The Main Thing (by Cristian Gafton, CEO at this module :-) 
 * (stolen from http://home.netscape.com)
 * (purloined again by Ron Peterson)
 */
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
				int argc, const char **argv)
{
    unsigned int ctrl;
    struct authtok_options options;

    D(("called."));

    options.retry_times = CO_RETRY_TIMES;
    options.use_authtok = CO_USE_AUTHTOK;
    memset(options.prompt_type, 0, BUFSIZ);
    strcpy(options.prompt_type,"UNIX");

    ctrl = _pam_parse(&options, argc, argv);

    if (flags & PAM_PRELIM_CHECK) {
       /* Check for PostgreSQL database connection */       

       D(("prelim check"));

       /* if we can extablish a connection */
       return PAM_SUCCESS;

       /* do this instead of abort??? */
       /* return PAM_TRY_AGAIN; */

       if (ctrl & PAM_DEBUG_ARG)
          _pam_log(LOG_NOTICE, "Can not connect to %s",
                   "A Database Name");
          return PAM_ABORT;
        
       /* Not reached */
       return PAM_SERVICE_ERR;

    } else if (flags & PAM_UPDATE_AUTHTOK) {
        int retval;
        char *token1, *token2, *oldtoken;
        struct pam_message msg[1],*pmsg[1];
        struct pam_response *resp;
        char prompt[BUFSIZ];

        D(("do update"));

        retval = pam_get_item(pamh, PAM_OLDAUTHTOK,
                              (const void **)&oldtoken);
        if (retval != PAM_SUCCESS) {
            if (ctrl & PAM_DEBUG_ARG)
                _pam_log(LOG_ERR,"Can not get old passwd");
            oldtoken=NULL;
            retval = PAM_SUCCESS;
        }

        do {        
           /*
            * make sure nothing inappropriate gets returned
            */
           token1 = token2 = NULL;
        
           if (!options.retry_times) {
              D(("returning %s because maxtries reached",
                 pam_strerror(pamh, retval)));
              return retval;
           }

           /* Planned modus operandi:
            * Get a passwd.
            * If okay get it a second time. 
            * Check to be the same with the first one.
            * set PAM_AUTHTOK and return
            */

           if (options.use_authtok == 1) {
              const char *item = NULL;

              retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &item);
              if (retval != PAM_SUCCESS) {
                 /* very strange. */
                 _pam_log(LOG_ALERT
                          ,"pam_get_item returned error to pam_authtok");
              } else if (item != NULL) {      /* we have a password! */
                 token1 = x_strdup(item);
                 item = NULL;
              } else {
                 retval = PAM_AUTHTOK_RECOVER_ERR;         /* didn't work */
              }

           } else {
              /* Prepare to ask the user for the first time */
              memset(prompt,0,sizeof(prompt));
              snprintf(prompt,sizeof(prompt),PROMPT1,
                       options.prompt_type, options.prompt_type[0]?" ":"");
              pmsg[0] = &msg[0];
              msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
              msg[0].msg = prompt;

              resp = NULL;
              retval = converse(pamh, ctrl, 1, pmsg, &resp);
              if (resp != NULL) {
                 /* interpret the response */
                 if (retval == PAM_SUCCESS) {     /* a good conversation */
                    token1 = x_strdup(resp[0].resp);
                    if (token1 == NULL) {
                       _pam_log(LOG_NOTICE,
                                "could not recover authentication token 1");
                       retval = PAM_AUTHTOK_RECOVER_ERR;
                    }
                 }
                 /*
                  * tidy up the conversation (resp_retcode) is ignored
                  */
                 _pam_drop_reply(resp, 1);
              } else {
                 retval = (retval == PAM_SUCCESS) ?
                    PAM_AUTHTOK_RECOVER_ERR:retval ;
              }
           }

           if (retval != PAM_SUCCESS) {
              if (ctrl && PAM_DEBUG_ARG)
                 _pam_log(LOG_DEBUG,"unable to obtain a password");
              continue;
           }

           /* Now we have a passwd. Ask for it once again */

           if (options.use_authtok == 0) {
              bzero(prompt,sizeof(prompt));
              snprintf(prompt,sizeof(prompt),PROMPT2,
                       options.prompt_type, options.prompt_type[0]?" ":"");
              pmsg[0] = &msg[0];
              msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
              msg[0].msg = prompt;

              resp = NULL;
              retval = converse(pamh, ctrl, 1, pmsg, &resp);
              if (resp != NULL) {
                 /* interpret the response */
                 if (retval == PAM_SUCCESS) {     /* a good conversation */
                    token2 = x_strdup(resp[0].resp);
                    if (token2 == NULL) {
                       _pam_log(LOG_NOTICE,
                                "could not recover authentication token 2");
                       retval = PAM_AUTHTOK_RECOVER_ERR;
                    }
                 }
                 /*
                  * tidy up the conversation (resp_retcode) is ignored
                  */
                 _pam_drop_reply(resp, 1);
              } else {
                 retval = (retval == PAM_SUCCESS) ?
                    PAM_AUTHTOK_RECOVER_ERR:retval ;
              }

              if (retval != PAM_SUCCESS) {
                 if (ctrl && PAM_DEBUG_ARG)
                    _pam_log(LOG_DEBUG
                             ,"unable to obtain the password a second time");
                 continue;
              }

              /* Hopefully now token1 and token2 the same password ... */
              if (strcmp(token1,token2) != 0) {
                 /* tell the user */
                 make_remark(pamh, ctrl, PAM_ERROR_MSG, MISTYPED_PASS);
                 token1 = _pam_delete(token1);
                 token2 = _pam_delete(token2);
                 pam_set_item(pamh, PAM_AUTHTOK, NULL);
                 if (ctrl & PAM_DEBUG_ARG)
                    _pam_log(LOG_NOTICE,"Password mistyped");
                 retval = PAM_AUTHTOK_RECOVER_ERR;
                 continue;
              }
        
              /* Yes, the password was typed correct twice
               * we store this password as an item
               */

              {
                 const char* item = NULL;
                 const char* user;
                 
                 retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
                 if (retval != PAM_SUCCESS) {
                    if (ctrl & PAM_DEBUG_ARG) {
                       _pam_log(LOG_ERR,"Can not get username");
                       return PAM_AUTHTOK_ERR;
                    }
                 }

                 retval = pam_set_item(pamh, PAM_AUTHTOK, token1);

                 if( retval == PAM_SUCCESS ) {
                    retval = save_password( user, oldtoken, token1 );
                 }

                 /* clean up */
                 token1 = _pam_delete(token1);
                 token2 = _pam_delete(token2);

                 if ( (retval != PAM_SUCCESS) ||
                      ((retval = pam_get_item(pamh, PAM_AUTHTOK,
                                              (const void **)&item)
                         ) != PAM_SUCCESS) ) {
                    _pam_log(LOG_CRIT, "error manipulating password");
                    /* unsave password here...? */
                    continue;
                 }
                 
                 item = NULL;                 /* break link to password */
                 return PAM_SUCCESS;
              }
           }
        } while (options.retry_times--);

    } else {
       if (ctrl & PAM_DEBUG_ARG)
          _pam_log(LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
       return PAM_SERVICE_ERR;
    }

    /* Not reached */
    return PAM_SERVICE_ERR;                            
}


#ifdef PAM_STATIC
/* static module data */
struct pam_module _pam_authtok_modstruct = {
     "pam_authtok",
     NULL,
     NULL,
     NULL,
     NULL,
     NULL,
     pam_sm_chauthtok
};
#endif

/*
 * Copyright (c) Ron Peterson <ron.peterson@yellowbank.com> 2006
 * 
 * GPL verbage here
 *
 */

/*
 * Copyright (c) Cristian Gafton <gafton@redhat.com>, 1996.
 *                                              All rights reserved
 *
 * 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.
 *
 * The following copyright was appended for the long password support
 * added with the libpam 0.58 release:
 *
 * Modificaton Copyright (c) Philip W. Dalrymple III <pwd@mdtsoft.com>
 *       1997. All rights reserved
 *
 * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO
 * 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.
 */

