diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2006-09-05 21:36:24 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2006-09-05 21:36:24 +0000 |
commit | 851805013698482ffe9347431f45c627caa15cbd (patch) | |
tree | e4cf89d4fd744dc2ddf73c7e085b36deca83529b /pam_sql | |
parent | bd863c9bbdc7e73ef6020558519da032df38d59f (diff) | |
download | pam-modules-851805013698482ffe9347431f45c627caa15cbd.tar.gz pam-modules-851805013698482ffe9347431f45c627caa15cbd.tar.bz2 |
*** empty log message ***
git-svn-id: file:///svnroot/pam-modules/trunk@31 56984be4-0537-0410-a56c-fcb268c96130
Diffstat (limited to 'pam_sql')
-rw-r--r-- | pam_sql/.cvsignore | 8 | ||||
-rw-r--r-- | pam_sql/Makefile.am | 55 | ||||
-rw-r--r-- | pam_sql/pam_mysql.c | 383 | ||||
-rw-r--r-- | pam_sql/pam_pgsql.c | 230 | ||||
-rw-r--r-- | pam_sql/pam_sql.c | 443 | ||||
-rw-r--r-- | pam_sql/pam_sql.h | 27 | ||||
-rw-r--r-- | pam_sql/sha1.c | 151 | ||||
-rw-r--r-- | pam_sql/sha1.h | 29 |
8 files changed, 1326 insertions, 0 deletions
diff --git a/pam_sql/.cvsignore b/pam_sql/.cvsignore new file mode 100644 index 0000000..28aee1d --- /dev/null +++ b/pam_sql/.cvsignore @@ -0,0 +1,8 @@ +.deps +.libs +Makefile +Makefile.in +pam_mysql.la +pam_mysql.lo +pam_pgsql.la +pam_pgsql.lo diff --git a/pam_sql/Makefile.am b/pam_sql/Makefile.am new file mode 100644 index 0000000..5172b99 --- /dev/null +++ b/pam_sql/Makefile.am @@ -0,0 +1,55 @@ +# Copyright (C) 2005, 2006 Sergey Poznyakoff +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +pamdir=@PAMDIR@ +pam_PROGRAMS = @SQL_MODULES@ +AM_INSTALLCHECK_STD_OPTIONS_EXEMPT = pam_mysql.la pam_pgsql.la +EXTRA_PROGRAMS = pam_mysql.la pam_pgsql.la +EXTRA_DIST = sha1.h sha1.c pam_sql.c +pam_mysql_la_SOURCES = pam_mysql.c pam_sql.c +pam_mysql_la_LDADD = -lpam @MYSQLLIBS@ +pam_pgsql_la_SOURCES = pam_pgsql.c pam_sql.c +pam_pgsql_la_LDADD = -lpam @PGSQLLIBS@ +AM_LDFLAGS = -version-info @VI_CURRENT@:@VI_REVISION@:@VI_AGE@ + +AM_CPPFLAGS=-DSYSCONFDIR=\"${sysconfdir}\" +INCLUDES=@PAM_COMMON_INCLUDES@ +NORMAL_UNINSTALL = list='$(pam_PROGRAMS)' ;\ + for mod in $$list ;\ + do \ + name=`expr $$mod : '\(.*\)\.la'`; \ + rm -f $(DESTDIR)$(pamdir)/$${name}.a \ + $(DESTDIR)$(pamdir)/$${name}.so.@VI_CURRENT@.@VI_REVISION@.@VI_AGE@; \ + done + +pam_mysql.lo: $(srcdir)/pam_sql.c $(srcdir)/sha1.c $(srcdir)/sha1.h +pam_pgsql.lo: $(srcdir)/pam_sql.c + +pam_mysql.lo: $(srcdir)/pam_mysql.c + $(LIBTOOL) --mode=compile $(CC) -o$@ -c -DHAVE_CONFIG_H \ + $(CFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(INCLUDES) \ + -DMODULE_NAME=\"pam_mysql\" $< + +pam_pgsql.lo: $(srcdir)/pam_pgsql.c + $(LIBTOOL) --mode=compile $(CC) -o$@ -c -DHAVE_CONFIG_H \ + $(CFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(INCLUDES) \ + -DMODULE_NAME=\"pam_pgsql\" $< + +pam_mysql.la$(EXEEXT): pam_mysql.lo + $(LIBTOOL) --mode=link $(CC) -module -export-dynamic \ + $(AM_LDFLAGS) $(pam_mysql_la_LDFLAGS) $(LDFLAGS) \ + -o $@ $< $(pam_mysql_la_LDADD) $(AM_LDADD) $(LDADD) \ + -rpath $(pamdir) + +pam_pgsql.la$(EXEEXT): pam_pgsql.lo + $(LIBTOOL) --mode=link $(CC) -module -export-dynamic \ + $(AM_LDFLAGS) $(pam_pgsql_la_LDFLAGS) $(LDFLAGS) \ + -o $@ $< $(pam_pgsql_la_LDADD) $(AM_LDADD) $(LDADD) \ + -rpath $(pamdir) diff --git a/pam_sql/pam_mysql.c b/pam_sql/pam_mysql.c new file mode 100644 index 0000000..6222b9b --- /dev/null +++ b/pam_sql/pam_mysql.c @@ -0,0 +1,383 @@ +/* This file is part of pam-modules. + Copyright (C) 2005, 2006 Sergey Poznyakoff + + 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#if defined(HAVE_CONFIG_H) +# include <config.h> +#endif +#ifdef HAVE__PAM_ACONF_H +#include <security/_pam_aconf.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <mysql/mysql.h> + +#include "pam_sql.c" +#include "sha1.h" +#include "sha1.c" + +char * +sql_expand_query (MYSQL *mysql, + const char *query, const char *user, const char *pass) +{ + char *p, *q, *res; + int len; + char *esc_user = NULL; + char *esc_pass = NULL; + + if (!query) + return NULL; + + /* Compute resulting query length */ + for (len = 0, p = (char *) query; *p; ) { + if (*p == '%') { + if (p[1] == 'u') { + int size = 2*strlen(user)+1; + esc_user = malloc(size); + if (!esc_user) + return NULL; + mysql_real_escape_string(mysql, + esc_user, + user, size); + + len += strlen (esc_user); + p += 2; + } if (p[1] == 'u') { + int size = 2*strlen(pass)+1; + esc_pass = malloc(len); + if (!esc_pass) + return NULL; + mysql_real_escape_string(mysql, + esc_pass, + pass, size); + + len += strlen (esc_pass); + p += 2; + } else if (p[1] == '%') { + len++; + p += 2; + } else { + len++; + p++; + } + } else { + len++; + p++; + } + } + + res = malloc (len + 1); + if (!res) { + free (esc_user); + free (esc_pass); + return res; + } + + for (p = (char *) query, q = res; *p; ) { + if (*p == '%') { + switch (*++p) { + case 'u': + strcpy (q, esc_user); + q += strlen (q); + p++; + break; + + case 'p': + strcpy (q, esc_pass); + q += strlen (q); + p++; + break; + + case '%': + *q++ = *p++; + break; + + default: + *q++ = *p++; + } + } else + *q++ = *p++; + } + *q = 0; + + free (esc_user); + free (esc_pass); + return res; +} + + +/* MySQL scrambled password support */ + +/* Convert a single hex digit to corresponding number */ +static unsigned +digit_to_number (char c) +{ + return (unsigned) (c >= '0' && c <= '9' ? c-'0' : + c >= 'A' && c <= 'Z' ? c-'A'+10 : + c-'a'+10); +} + +/* Extract salt value from MySQL scrambled password. + + WARNING: The code assumes that + 1. strlen (password) % 8 == 0 + 2. number_of_entries (RES) = strlen (password) / 8 + + For MySQL >= 3.21, strlen(password) == 16 */ +static void +get_salt_from_scrambled (unsigned long *res, const char *password) +{ + res[0] = res[1] = 0; + while (*password) { + unsigned long val = 0; + unsigned i; + + for (i = 0; i < 8 ; i++) + val = (val << 4) + digit_to_number (*password++); + *res++ = val; + } +} + +/* Scramble a plaintext password */ +static void +scramble_password (unsigned long *result, const char *password) +{ + unsigned long nr = 1345345333L, add = 7, nr2 = 0x12345671L; + unsigned long tmp; + + for (; *password ; password++) { + if (*password == ' ' || *password == '\t') + continue; + tmp = (unsigned long) (unsigned char) *password; + nr ^= (((nr & 63) + add) * tmp)+ (nr << 8); + nr2 += (nr2 << 8) ^ nr; + add += tmp; + } + + result[0] = nr & (((unsigned long) 1L << 31) -1L); + result[1] = nr2 & (((unsigned long) 1L << 31) -1L); +} + +static void +octet2hex (char *to, const unsigned char *str, unsigned len) +{ + const unsigned char *str_end= str + len; + static char d[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + for ( ; str != str_end; ++str) { + *to++ = d[(*str & 0xF0) >> 4]; + *to++ = d[*str & 0x0F]; + } + *to= '\0'; +} + +#define SHA1_HASH_SIZE 20 +static int +make_mysql_4x_password (char *to, const char *message) +{ + SHA1_CTX sha1_context; + unsigned char hash_stage2[SHA1_HASH_SIZE]; + + /* stage 1: hash password */ + SHA1Init(&sha1_context); + SHA1Update(&sha1_context, message, strlen (message)); + SHA1Final(to, &sha1_context); + + /* stage 2: hash stage1 output */ + SHA1Init(&sha1_context); + SHA1Update(&sha1_context, to, SHA1_HASH_SIZE); + SHA1Final(hash_stage2, &sha1_context); + + /* convert hash_stage2 to hex string */ + *to++ = '*'; + octet2hex (to, hash_stage2, SHA1_HASH_SIZE); +} + +static int +mu_check_mysql_4x_password (const char *scrambled, const char *message) +{ + char to[2*SHA1_HASH_SIZE + 2]; + + make_mysql_4x_password (to, message); + /* Compare both strings */ + return memcmp (to, scrambled, strlen (scrambled)); +} + +/* Check whether a plaintext password MESSAGE matches MySQL scrambled password + PASSWORD */ +static int +mu_check_mysql_scrambled_password (const char *scrambled, const char *message) +{ + unsigned long hash_pass[2], hash_message[2]; + char buf[17]; + + if (strlen (scrambled) < 16) + return 1; + if (strlen (scrambled) > 16) { + const char *p; + /* Try to normalize it by cutting off trailing whitespace */ + for (p = scrambled + strlen (scrambled) - 1; + p > scrambled && isspace (*p); p--) + ; + if (p - scrambled != 15) + return 1; + memcpy (buf, scrambled, 16); + buf[17] = 0; + scrambled = buf; + } + + get_salt_from_password (hash_pass, scrambled); + scramble_password (hash_message, message); + return !(hash_message[0] == hash_pass[0] + && hash_message[1] == hash_pass[1]); +} + +static int +check_mysql_pass(const char *sqlpass, const char *userpass) +{ + if (mu_check_mysql_4x_password(sqlpass, userpass) == 0 + || mu_check_mysql_scrambled_password (sqlpass, userpass) == 0) + return PAM_SUCCESS; + else + return PAM_AUTH_ERR; +} + +static int +check_query_result(MYSQL *mysql, const char *pass) +{ + MYSQL_RES *result; + int rc = PAM_AUTH_ERR; + + result = mysql_store_result(mysql); + if (!result) { + _pam_err(LOG_ERR, "MySQL: query returned 0 tuples"); + rc = PAM_AUTH_ERR; + } else { + MYSQL_ROW row; + long n = mysql_num_rows(result); + if (n != 1) { + _pam_err(LOG_WARNING, + "MySQL: query returned %d tuples", n); + if (n == 0) { + mysql_free_result(result); + return PAM_AUTH_ERR; + } + } + n = mysql_num_fields(result); + if (n != 1) { + _pam_err(LOG_WARNING, + "MySQL: query returned %d fields", n); + } + + row = mysql_fetch_row(result); + chop(row[0]); + DEBUG(100,("Obtained password value: %s", row[0])); + if (strcmp(row[0], crypt(pass, row[0])) == 0) + rc = PAM_SUCCESS; + if (rc != PAM_SUCCESS + && check_boolean_config ("allow-mysql-pass", 1)) + rc = check_mysql_pass (row[0], pass); + if (rc != PAM_SUCCESS + && check_boolean_config ("allow-plaintext-pass", 0)) { + if (strcmp (row[0], pass) == 0) + rc = PAM_SUCCESS; + } + } + mysql_free_result(result); + + return rc; +} + +int +verify_user_pass(const char *username, const char *password) +{ + MYSQL mysql; + char *socket_path = NULL; + char *hostname; + char *login; + char *pass; + char *db; + char *port; + int portno; + char *query, *exquery; + char *p; + MYSQL_RES *result; + int rc; + + hostname = find_config("host"); + CHKVAR(hostname); + if (hostname[0] == '/') { + socket_path = hostname; + hostname = "localhost"; + } + + port = find_config("port"); + CHKVAR(port); + portno = strtoul (port, &p, 0); + if (*p) { + _pam_err(LOG_ERR, "Invalid port number: %s", port); + return PAM_SERVICE_ERR; + } + + login = find_config("login"); + CHKVAR(login); + + pass = find_config("pass"); + CHKVAR(pass); + + db = find_config("db"); + CHKVAR(db); + + query = find_config("query"); + CHKVAR(query); + + exquery = sql_expand_query (&mysql, query, username, password); + if (!exquery) { + _pam_err(LOG_ERR, "cannot expand query"); + return PAM_SERVICE_ERR; + } + + mysql_init(&mysql); + + if (!mysql_real_connect(&mysql, hostname, + login, pass, db, + portno, socket_path, 0)) { + _pam_err(LOG_ERR, "cannot connect to MySQL"); + mysql_close(&mysql); + free(exquery); + return PAM_SERVICE_ERR; + } + + if (mysql_query(&mysql, exquery)) { + _pam_err(LOG_ERR, "MySQL: %s", mysql_error(&mysql)); + mysql_close(&mysql); + free(exquery); + return PAM_SERVICE_ERR; + } + free(exquery); + + rc = check_query_result(&mysql, password); + + mysql_close(&mysql); + + return rc; +} + diff --git a/pam_sql/pam_pgsql.c b/pam_sql/pam_pgsql.c new file mode 100644 index 0000000..c2cc355 --- /dev/null +++ b/pam_sql/pam_pgsql.c @@ -0,0 +1,230 @@ +/* This file is part of pam-modules. + Copyright (C) 2005, 2006 Sergey Poznyakoff + + 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#if defined(HAVE_CONFIG_H) +# include <config.h> +#endif +#ifdef HAVE__PAM_ACONF_H +#include <security/_pam_aconf.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <libpq-fe.h> + +#include "pam_sql.c" + +static char * +sql_escape_string (const char *ustr) +{ + char *str, *q; + const unsigned char *p; + size_t len = strlen (ustr); +#define ESCAPABLE_CHAR "\\'\"" + + for (p = (const unsigned char *) ustr; *p; p++) { + if (strchr (ESCAPABLE_CHAR, *p)) + len++; + } + + str = malloc (len + 1); + if (!str) + return NULL; + + for (p = (const unsigned char *) ustr, q = str; *p; p++) { + if (strchr (ESCAPABLE_CHAR, *p)) + *q++ = '\\'; + *q++ = *p; + } + *q = 0; + return str; +} + +char * +sql_expand_query (const char *query, const char *user, const char *pass) +{ + char *p, *q, *res; + int len; + char *esc_user = NULL; + char *esc_pass = NULL; + + if (!query) + return NULL; + + /* Compute resulting query length */ + for (len = 0, p = (char *) query; *p; ) { + if (*p == '%') { + if (p[1] == 'u') { + esc_user = sql_escape_string(user); + len += strlen (esc_user); + p += 2; + } if (p[1] == 'u') { + esc_pass = sql_escape_string(pass); + len += strlen (esc_pass); + p += 2; + } else if (p[1] == '%') { + len++; + p += 2; + } else { + len++; + p++; + } + } else { + len++; + p++; + } + } + + res = malloc (len + 1); + if (!res) { + free (esc_user); + free (esc_pass); + return res; + } + + for (p = (char *) query, q = res; *p; ) { + if (*p == '%') { + switch (*++p) { + case 'u': + strcpy (q, esc_user); + q += strlen (q); + p++; + break; + + case 'p': + strcpy (q, esc_pass); + q += strlen (q); + p++; + break; + + case '%': + *q++ = *p++; + break; + + default: + *q++ = *p++; + } + } else + *q++ = *p++; + } + *q = 0; + + free (esc_user); + free (esc_pass); + return res; +} + +int +verify_user_pass(const char *username, const char *password) +{ + PGconn *pgconn; + PGresult *res; + char *hostname; + char *login; + char *pass; + char *db; + char *port; + char *query, *exquery; + char *p; + int rc; + + hostname = find_config("host"); + CHKVAR(hostname); + + port = find_config("port"); + CHKVAR(port); + + login = find_config("login"); + CHKVAR(login); + + pass = find_config("pass"); + CHKVAR(pass); + + db = find_config("db"); + CHKVAR(db); + + query = find_config("query"); + CHKVAR(query); + + exquery = sql_expand_query (query, username, password); + if (!exquery) { + _pam_err(LOG_ERR, "cannot expand query"); + return PAM_SERVICE_ERR; + } + + pgconn = PQsetdbLogin (hostname, port, NULL, NULL, + db, login, password); + if (PQstatus (pgconn) == CONNECTION_BAD) { + _pam_err(LOG_ERR, "cannot connect to database"); + return PAM_SERVICE_ERR; + } + + DEBUG(10,("Executing %s", exquery)); + res = PQexec (pgconn, exquery); + if (res == NULL) { + _pam_err(LOG_ERR, "PQexec: %s", PQerrorMessage(pgconn)); + rc = PAM_SERVICE_ERR; + } else if (PQresultStatus(res) != PGRES_TUPLES_OK) { + _pam_err(LOG_ERR, "PQexec: query did not return tuples"); + rc = PAM_SERVICE_ERR; + } else { + char *p; + int n; + + n = PQntuples(res); + DEBUG(20,("Returned %d tuples", n)); + if (n != 1) { + _pam_err(LOG_WARNING, + "PQexec: query returned %d tuples", n); + if (n == 0) { + PQclear(res); + PQfinish(pgconn); + return PAM_SERVICE_ERR; + } + } + + n = PQnfields(res); + DEBUG(20,("Returned %d fields", n)); + if (n != 1) { + _pam_err(LOG_WARNING, + "PQexec: query returned %d fields", n); + } + + p = PQgetvalue(res, 0, 0); + chop(p); + DEBUG(100,("Obtained password value: %s", p)); + + if (strcmp(p, crypt(password, p)) == 0) + rc = PAM_SUCCESS; + else if (rc != PAM_SUCCESS + && check_boolean_config ("allow-plaintext-pass", 0)) { + if (strcmp (p, pass) == 0) + rc = PAM_SUCCESS; + } else + rc = PAM_AUTH_ERR; + } + + PQclear(res); + PQfinish(pgconn); + + return rc; +} + diff --git a/pam_sql/pam_sql.c b/pam_sql/pam_sql.c new file mode 100644 index 0000000..97c7f62 --- /dev/null +++ b/pam_sql/pam_sql.c @@ -0,0 +1,443 @@ +/* This file is part of pam-modules. + Copyright (C) 2005, 2006 Sergey Poznyakoff + + 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +#if defined(HAVE_CONFIG_H) +# include <config.h> +#endif +#ifdef HAVE__PAM_ACONF_H +# include <security/_pam_aconf.h> +#endif +#include <security/pam_modules.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <syslog.h> +#include <errno.h> +#if defined(HAVE_CRYPT_H) +# include <crypt.h> +#endif + +/* indicate the following groups are defined */ +#define PAM_SM_AUTH + +#include <common.c> + +#define CHKVAR(v) \ + if (!(v)) { \ + _pam_err(LOG_ERR, "%s: %s not defined", config_file, #v); \ + return PAM_SERVICE_ERR; \ + } \ + DEBUG(100,("Config: %s=%s", #v, v)); + +/* logging */ +static void +_pam_vlog(int err, const char *format, va_list args) +{ + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + closelog(); +} + +static void +_pam_log(int err, const char *format, ...) +{ + va_list args; + + va_start(args, format); + _pam_vlog(err, format, args); + va_end(args); +} + +void +_pam_debug(char *format, ...) +{ + va_list args; + + va_start(args, format); + _pam_vlog(LOG_DEBUG, format, args); + va_end(args); +} + +int verify_user_pass(const char *username, const char *password); + +#define CNTL_DEBUG 0x0001 +#define CNTL_AUDIT 0x0002 +#define CNTL_AUTHTOK 0x0004 +#define CNTL_WAITDEBUG 0x0008 + +#define CNTL_SET_DEBUG_LEV(cntl,n) (cntl |= ((n)<<16)) +#define CNTL_DEBUG_LEV() (cntl_flags>>16) + +#define DEBUG(m,c) if (CNTL_DEBUG_LEV()>=(m)) _pam_debug c +#define AUDIT(c) if (cntl_flags&CNTL_AUDIT) _pam_debug c + +static int cntl_flags; +char *config_file = SYSCONFDIR "/pam_sql.conf"; + +#define XSTRDUP(s) (s) ? strdup(s) : NULL + +static void +make_str(pam_handle_t *pamh, const char *str, const char *name, char **ret) +{ + int retval; + char *newstr = XSTRDUP(str); + + retval = pam_set_data(pamh, name, (void *)newstr, _cleanup_string); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_CRIT, + "can't keep data [%s]: %s", + name, + pam_strerror(pamh, retval)); + _pam_delete(newstr); + } else { + *ret = newstr; + newstr = NULL; + } +} + +#define MAKE_STR(pamh, str, var) \ + make_str(pamh,str,#var,&var) + +static void +_pam_parse(int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + + if (!strncmp(*argv,"debug",5)) { + ctrl |= CNTL_DEBUG; + if ((*argv)[5] == '=') + CNTL_SET_DEBUG_LEV(ctrl,atoi(*argv+6)); + else + CNTL_SET_DEBUG_LEV(ctrl,1); + } else if (!strcmp(*argv,"audit")) + ctrl |= CNTL_AUDIT; + else if (!strcmp(*argv,"waitdebug")) + ctrl |= CNTL_WAITDEBUG; + else if (!strcmp(*argv,"use_authtok")) + ctrl |= CNTL_AUTHTOK; + else if (!strncmp(*argv, "config=", 7)) + config_file = (char*) (*argv + 7); + else { + _pam_log(LOG_ERR,"pam_parse: unknown option; %s", + *argv); + } + } + cntl_flags = ctrl; +} + +static int +_pam_get_password(pam_handle_t *pamh, char **password, const char *prompt) +{ + char *item, *token; + int retval; + struct pam_message msg[3], *pmsg[3]; + struct pam_response *resp; + int i, replies; + + DEBUG(100,("enter _pam_get_password")); + + if (cntl_flags & CNTL_AUTHTOK) { + /* + * get the password from the PAM item + */ + retval = pam_get_item(pamh, PAM_AUTHTOK, + (const void **) &item); + if (retval != PAM_SUCCESS) { + /* very strange. */ + _pam_log(LOG_ALERT, + "can't retrieve password item: %s", + pam_strerror(pamh, retval)); + return retval; + } else if (item != NULL) { + *password = item; + item = NULL; + return PAM_SUCCESS; + } else + return PAM_AUTHTOK_RECOVER_ERR; + } + + /* + * ask user for the password + */ + /* prepare to converse */ + + i = 0; + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = (const void*)prompt; + replies = 1; + + /* run conversation */ + resp = NULL; + token = NULL; + retval = converse(pamh, i, pmsg, &resp); + + if (resp != NULL) { + if (retval == PAM_SUCCESS) { /* a good conversation */ + token = XSTRDUP(resp[i - replies].resp); + DEBUG(10,("app returned [%s]", token)); + PAM_DROP_REPLY(resp, 1); + } else { + _pam_log(LOG_ERR, "conversation error: %s", + pam_strerror(pamh, retval)); + } + + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR : retval; + } + + if (retval == PAM_SUCCESS) { + /* + * keep password as data specific to this module. pam_end() + * will arrange to clean it up. + */ + retval = pam_set_data(pamh, "password", + (void *)token, + _cleanup_string); + if (retval != PAM_SUCCESS) { + _pam_log(LOG_CRIT, + "can't keep password: %s", + pam_strerror(pamh, retval)); + _pam_delete(token); + } else { + *password = token; + token = NULL; /* break link to password */ + } + } else { + _pam_log(LOG_ERR, + "unable to obtain a password: %s", + pam_strerror(pamh, retval)); + } + + DEBUG(100,("exit _pam_get_password: %d", retval)); + return retval; +} + + +/* Configuration */ +typedef struct config_env env_t; +struct config_env { + env_t *next; + char *name; + char *value; +}; +static env_t *config_env; + +/* + * Chop off trailing whitespace. Return length of the resulting string + */ +static int +chop(char *str) +{ + int len; + + for (len = strlen(str); len > 0 && isspace(str[len-1]); len--) + ; + str[len] = 0; + return len; +} + +char * +find_config(const char *name) +{ + env_t *env; + + for (env = config_env; env; env = env->next) + if (strcmp(env->name, name) == 0) + return env->value; + return NULL; +} + +void +free_config() +{ + env_t *env = config_env; + while (env) { + env_t *next = env->next; + free(env->name); + free(env); + env = next; + } +} + +static int +boolean_true_p(const char *value) +{ + return strcmp(value, "yes") == 0 + || strcmp(value, "true") == 0 + || strcmp(value, "t") == 0; +} + +static int +check_boolean_config(const char *name, int defval) +{ + const char *value = find_config(name); + if (value) + defval = boolean_true_p(value); + return defval; +} + +static int +read_config () +{ + FILE *fp; + char *p; + int rc = 0; + int line = 0; + char buf[128]; + + fp = fopen (config_file, "r"); + if (!fp) { + _pam_log(LOG_ERR, "cannot open configuration file `%s': %s", + config_file, strerror (errno)); + return 1; + } + + while (p = fgets (buf, sizeof buf, fp)) { + int len; + env_t *env; + + line++; + while (*p && isspace(*p)) + ; + len = strlen(p); + if (len == 0) + continue; + if (p[len-1] != '\n') { + _pam_log(LOG_EMERG, "%s:%d: string too long", + config_file, line); + continue; + } + + p[len-1] = 0; + chop(p); + + if (*p == 0 || *p == '#') + continue; + + env = malloc(sizeof *env); + if (!env) { + _pam_log(LOG_EMERG, "not enough memory"); + rc = 1; + break; + } + + env->name = strdup(p); + if (!env->name) { + _pam_log(LOG_EMERG, "not enough memory"); + free(env); + rc = 1; + break; + } + + for (p = env->name; *p && !isspace(*p); p++) + ; + for (; *p && isspace(*p); p++) + ; + if (!*p) { + _pam_log(LOG_EMERG, "%s:%d: not enough fields", + config_file, line); + free(env->name); + free(env); + continue; + } + env->value = p; + env->next = config_env; + config_env = env; + } + + fclose(fp); + return rc; +} + + + + +/* --- authentication management functions (only) --- */ + +PAM_EXTERN int +pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + const char *username; + char *password; + int retval = PAM_AUTH_ERR; + + /* parse arguments */ + _pam_parse(argc, argv); + + /* Get the username */ + retval = pam_get_user(pamh, &username, NULL); + if (retval != PAM_SUCCESS || !username) { + _pam_log(LOG_DEBUG, "can not get the username"); + return PAM_SERVICE_ERR; + } + + /* Get the password */ + if (_pam_get_password(pamh, &password, "Password:")) + return PAM_SERVICE_ERR; + + if (retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "Could not retrive user's password"); + return PAM_SERVICE_ERR; + } + + if (read_config()) + retval = PAM_SERVICE_ERR; + else + retval = verify_user_pass(username, password); + free_config(); + + switch (retval) { + case PAM_ACCT_EXPIRED: + _pam_log(LOG_NOTICE, "user '%s': account expired", username); + break; + case PAM_SUCCESS: + _pam_log(LOG_NOTICE, "user '%s' granted access", username); + break; + default: + _pam_log(LOG_NOTICE, "user '%s' failed to authenticate", + username); + } + + return retval; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_fshadow_modstruct = { + MODULE_NAME, + pam_sm_authenticate, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +#endif diff --git a/pam_sql/pam_sql.h b/pam_sql/pam_sql.h new file mode 100644 index 0000000..4b20a48 --- /dev/null +++ b/pam_sql/pam_sql.h @@ -0,0 +1,27 @@ +/* This file is part of pam-modules. + Copyright (C) 2005, 2006 Sergey Poznyakoff + + 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., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + + +#define __s_cat2__(a,b) a ## b + +#define _pam_debug __s_cat2__(MODULE_NAME,_pam_debug) +#define cntl_flags __s_cat2__(MODULE_NAME,_cntl_flags) + + + + diff --git a/pam_sql/sha1.c b/pam_sql/sha1.c new file mode 100644 index 0000000..aee53f4 --- /dev/null +++ b/pam_sql/sha1.c @@ -0,0 +1,151 @@ +/* + * SHA-1 in C + * By Steve Reid <steve@edmweb.com> + * 100% Public Domain + * + * Version: $Id$ + */ + +#include <string.h> +#include <sys/types.h> +#include <netinet/in.h> + +#define blk0(i) (block->l[i] = htonl(block->l[i])) + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ + +#define blk0(i) (block->l[i] = htonl(block->l[i])) + +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + |