aboutsummaryrefslogtreecommitdiff
path: root/pam_sql
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2006-09-05 21:36:24 +0000
committerSergey Poznyakoff <gray@gnu.org.ua>2006-09-05 21:36:24 +0000
commit851805013698482ffe9347431f45c627caa15cbd (patch)
treee4cf89d4fd744dc2ddf73c7e085b36deca83529b /pam_sql
parentbd863c9bbdc7e73ef6020558519da032df38d59f (diff)
downloadpam-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/.cvsignore8
-rw-r--r--pam_sql/Makefile.am55
-rw-r--r--pam_sql/pam_mysql.c383
-rw-r--r--pam_sql/pam_pgsql.c230
-rw-r--r--pam_sql/pam_sql.c443
-rw-r--r--pam_sql/pam_sql.h27
-rw-r--r--pam_sql/sha1.c151
-rw-r--r--pam_sql/sha1.h29
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))
+