/* pam_informix.ec copyright 1999 by Carsten Koester * * pam authentication module fetching the user's encrypted password from * an Informix SQL database. * * This code is based on Charl P. Botha's pam_pwdfile module. * * Note: The Informix ESQL/C precompiler is required to compile this. * * To build, run * esql -e pam_informix.ec * gcc -fPIC -c pam_informix.c -I${INFORMIXDIR}/incl/esql * ld -x --shared -o pam_informix.so pam_informix.o -lpam -lcrypt \ * -L${INFORMIXDIR}/lib/esql -L${INFORMIXDIR}/lib -lifsql \ * -lifasf -lifgen -lifos -lifgls -lm -ldl -lcrypt -lifglx \ * ${INFORMIXDIR}/lib/esql/checkapi.o * * Include -DDEBUG in the compiler flags to enable debugging output in SYSLOG. * * Usage: * * auth required pam_informix.so INFORMIXDIR INFORMIXSERVER DBNAME DBUSER \ * DBPASSWD TABLENAME USERNAMEFIELD PASSWORDFIELD * * where * - INFORMIXDIR is the Informix base directory on the local machine. This * is required since we cannot be sure all programs pass the INFORMIXDIR * environment variable to this module. * - INFORMIXSERVER is the informix server containing the database. * - DBNAME is the name of the database containing the passwords. * - DBUSER is the username to use when connecting to the database server. * - DBPASSWD is the password to use when connecting to the database server. * - TABLENAME is the name of the table (or view) containing the * username/password pairs * - USERNAMEFIELD is the name of the column containing the username * - PASSWORDFIELD is the name of the column containing the unix-encrypted * password. * * 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. */ #ifndef LINUX #include #endif /* LINUX */ #define PAM_SM_AUTH #include #include #include #include #include #include #define _XOPEN_SOURCE #include /* unistd.h does not declare this as it should */ extern char *crypt(const char *key, const char *salt); #define CRYPTEDPWD_LEN 13 EXEC SQL define CRYPTPW_LEN 13; #ifdef DEBUG # define D(a) a; #else # define D(a) {} #endif /* prototypes */ int converse(pam_handle_t *, int, struct pam_message **, struct pam_response **); int _set_auth_tok(pam_handle_t *, int, int, const char **); /* logging function ripped from pam_listfile.c */ static void _pam_log(int err, const char *format, ...) { va_list args; va_start(args, format); openlog("pam_informix", LOG_CONS|LOG_PID, LOG_AUTH); vsyslog(err, format, args); va_end(args); closelog(); } /* this function ripped from pam_unix/support.c */ 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; } /* this function ripped from pam_unix/support.c */ int _set_auth_tok( pam_handle_t *pamh, int flags, int argc, const char **argv ) { int retval; char *p; struct pam_message msg[1],*pmsg[1]; struct pam_response *resp; /* set up conversation call */ pmsg[0] = &msg[0]; msg[0].msg_style = PAM_PROMPT_ECHO_OFF; msg[0].msg = "Password: "; resp = NULL; if ( ( retval = converse( pamh, 1 , pmsg, &resp ) ) != PAM_SUCCESS ) return retval; if ( resp ) { if ( ( flags & PAM_DISALLOW_NULL_AUTHTOK ) && resp[0].resp == NULL ) { free( resp ); return PAM_AUTH_ERR; } p = resp[ 0 ].resp; /* This could be a memory leak. If resp[0].resp is malloc()ed, then it has to be free()ed! -- alex */ resp[ 0 ].resp = NULL; } else return PAM_CONV_ERR; free( resp ); pam_set_item( pamh, PAM_AUTHTOK, p ); return PAM_SUCCESS; } /* expected hook for auth service */ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval; char *password; int user_in_db = 0; char informixdir[255]; char salt[3]; EXEC SQL BEGIN DECLARE SECTION; const char *name; char cryptpw[ CRYPTPW_LEN + 1 ]; char informixserver[20]; char dbname[20]; char dbuser[20]; char dbpass[20]; char tablename[30]; char usernamefield[30]; char passwordfield[30]; char sqlstatement[255]; EXEC SQL END DECLARE SECTION; /* * First, check and get arguments. * There must be 8 args: * - informixdir * - informixserver * - db name * - db username * - db password * - table name * - field name containing the "username" value * - field name containing the unix-encrypted password */ if (argc != 8) { D(_pam_log(LOG_ERR, "found %d arguments, expected 8", argc)); _pam_log(LOG_ERR, "invalid number of arguments"); return PAM_AUTHINFO_UNAVAIL; } strncpy (informixdir, argv[0], 255); strncpy (informixserver, argv[1], 20); strncpy (dbname, argv[2], 20); strncpy (dbuser, argv[3], 20); strncpy (dbpass, argv[4], 20); strncpy (tablename, argv[5], 30); strncpy (usernamefield, argv[6], 30); strncpy (passwordfield, argv[7], 30); D(_pam_log(LOG_ERR, "informixdir = %s", informixdir)); D(_pam_log(LOG_ERR, "informixserver = %s", informixserver)); D(_pam_log(LOG_ERR, "dbname = %s", dbname)); D(_pam_log(LOG_ERR, "dbuser = %s", dbuser)); D(_pam_log(LOG_ERR, "dbpass = %s", dbpass)); D(_pam_log(LOG_ERR, "tablename = %s", tablename)); D(_pam_log(LOG_ERR, "usernamefield = %s", usernamefield)); D(_pam_log(LOG_ERR, "passwordfield = %s", passwordfield)); /* * Second, set env variables for the INFORMIX environment... */ setenv ("INFORMIXDIR",informixdir,1); setenv ("INFORMIXSERVER",informixserver,1); /* get user name */ if ((retval = pam_get_user(pamh,&name,"login: ")) != PAM_SUCCESS) { _pam_log(LOG_ERR, "username not found"); return retval; } /* DEBUG */ D(_pam_log(LOG_ERR,"username is %s", name)); /* get password - code from pam_unix_auth.c */ pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); if (!password) { retval = _set_auth_tok(pamh, flags, argc, argv); if (retval!=PAM_SUCCESS) { return retval; } } pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); if ((retval = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password)) != PAM_SUCCESS) { _pam_log(LOG_ERR, "auth token not found"); return retval; } /* DEBUG */ D(_pam_log(LOG_ERR,"got password from user", password)); /* now crypt password and compare to the user entry in the password file */ /* first make sure password is long enough -- may I do this? */ if (strlen(password)<2 || password==NULL) { _pam_log(LOG_ERR,"password too short or NULL"); return PAM_AUTH_ERR; } /* get the crypted password corresponding to this user */ /* * Connect to Informix server */ EXEC SQL CONNECT TO :dbname USER :dbuser USING :dbpass ; D(_pam_log(LOG_ERR,"Connected to DB, SQLSTATE is %s", SQLSTATE)); if (strncmp(SQLSTATE, "0", 1) != 0) { _pam_log(LOG_ERR,"Error connecting to database"); return PAM_AUTHINFO_UNAVAIL; } /* * Prepare and execute select statement */ snprintf(sqlstatement, 255, "select %s from %s where %s=\"%s\"", passwordfield, tablename, usernamefield, name); D(_pam_log(LOG_ERR,"SQL statement is: %s", sqlstatement)); EXEC SQL PREPARE getpw FROM :sqlstatement; D(_pam_log(LOG_ERR,"Prepared statement, SQLSTATE is %s", SQLSTATE)); if (strncmp(SQLSTATE, "0", 1) != 0) { _pam_log(LOG_ERR,"Error preparing statement"); EXEC SQL DISCONNECT DEFAULT; return PAM_AUTHINFO_UNAVAIL; } EXEC SQL EXECUTE getpw INTO :cryptpw; D(_pam_log(LOG_ERR,"Executed statement, SQLSTATE is %s", SQLSTATE)); if (strncmp(SQLSTATE, "0", 1) != 0) { _pam_log(LOG_ERR,"Error executing SQL statement"); EXEC SQL DISCONNECT DEFAULT; return PAM_AUTHINFO_UNAVAIL; } /* check if data was returned */ user_in_db = !(strncmp(SQLSTATE, "00", 2)); /* * disconnect. */ EXEC SQL DISCONNECT DEFAULT; if (user_in_db == 0) { _pam_log(LOG_ERR,"user not in database"); return PAM_AUTH_ERR; } /* * Informix stuff done. */ /* DEBUG */ D(_pam_log(LOG_ERR,"got crypted password == %s", cryptpw)); /* extract the salt */ salt[0] = cryptpw[0]; salt[1] = cryptpw[1]; salt[2] = '\0'; /* DEBUG */ D(_pam_log(LOG_ERR,"user password crypted is %s", crypt(password,salt))); /* if things don't match up, complain */ cryptpw[CRYPTEDPWD_LEN] = '\0'; if (strcmp(crypt(password,salt),cryptpw)!=0) { _pam_log(LOG_ERR,"wrong password for user %s",name); return PAM_AUTH_ERR; } /* DEBUG */ D(_pam_log(LOG_ERR,"passwords match")); /* we've gotten here, i.e. authentication was sucessful! */ return PAM_SUCCESS; } /* another expected hook */ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } #ifdef PAM_STATIC struct pam_module _pam_listfile_modstruct = { "pam_informix", pam_sm_authenticate, pam_sm_setcred, NULL, NULL, NULL, NULL, }; #endif