aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2012-05-13 09:48:57 +0000
committerSergey Poznyakoff <gray@gnu.org.ua>2012-05-13 09:48:57 +0000
commit1a80b647d6d40f7ce39afb2575ea5d8f9766f9da (patch)
tree1b3b70b849f27b102623b29e0f71381c4ca67312
parent993eff136613ba7964bd3747630cf06ced3d940c (diff)
downloadpam-modules-1a80b647d6d40f7ce39afb2575ea5d8f9766f9da.tar.gz
pam-modules-1a80b647d6d40f7ce39afb2575ea5d8f9766f9da.tar.bz2
Revamp build system. Add pam_ldaphome module.
* configure.ac: Test for ldap. (AC_OUTPUT): Create pam_ldaphome/Makefile. * acinclude.m4 (PM_ENABLE): Declare PAM_COND_<item> conditional in addition to BUILD_PAM_<item> substitution variable. * Makefile.am (SUBDIRS): Include most modules via conditionally defined Makefile variables. * lib/graypam.h (gray_env): New struct. (gray_env_get,gray_env_free,gray_env_read) (gray_boolean_true_p): New protos. * lib/env.c: New file. * lib/Makefile.am (libgraypam_la_SOURCES): Add env.c * pam_fshadow/Makefile.am: Remove BUILD_PAM_FSHADOW substitution. * pam_regex/Makefile.am: Remove BUILD_PAM_REGEX substitution. * pam_sql/pam_sql.c (free_config, boolean_true_p) (read_config): Remove. Use gray_env_* functions instead. All uses updated. * pam_regex/pam_regex.c: Fix typo. * pam_ldaphome/Makefile.am: New file. git-svn-id: file:///svnroot/pam-modules/trunk@118 56984be4-0537-0410-a56c-fcb268c96130
-rw-r--r--ChangeLog29
-rw-r--r--Makefile.am22
-rw-r--r--acinclude.m414
-rw-r--r--configure.ac20
-rw-r--r--lib/Makefile.am1
-rw-r--r--lib/env.c168
-rw-r--r--lib/graypam.h14
-rw-r--r--pam_fshadow/Makefile.am4
-rw-r--r--pam_ldaphome/Makefile.am23
-rw-r--r--pam_ldaphome/pam_ldaphome.c865
-rw-r--r--pam_regex/Makefile.am5
-rw-r--r--pam_regex/pam_regex.c2
-rw-r--r--pam_sql/pam_sql.c169
13 files changed, 1155 insertions, 181 deletions
diff --git a/ChangeLog b/ChangeLog
index dd34f2f..ac0dedb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,32 @@
+2012-05-13 Sergey Poznyakoff <gray@gnu.org.ua>
+
+ Revamp build system. Add pam_ldaphome module.
+
+ * configure.ac: Test for ldap.
+ (AC_OUTPUT): Create pam_ldaphome/Makefile.
+ * acinclude.m4 (PM_ENABLE): Declare PAM_COND_<item>
+ conditional in addition to BUILD_PAM_<item> substitution
+ variable.
+ * Makefile.am (SUBDIRS): Include most modules via
+ conditionally defined Makefile variables.
+
+ * lib/graypam.h (gray_env): New struct.
+ (gray_env_get,gray_env_free,gray_env_read)
+ (gray_boolean_true_p): New protos.
+ * lib/env.c: New file.
+ * lib/Makefile.am (libgraypam_la_SOURCES): Add env.c
+
+ * pam_fshadow/Makefile.am: Remove BUILD_PAM_FSHADOW
+ substitution.
+ * pam_regex/Makefile.am: Remove BUILD_PAM_REGEX
+ substitution.
+ * pam_sql/pam_sql.c (free_config, boolean_true_p)
+ (read_config): Remove. Use gray_env_* functions
+ instead. All uses updated.
+ * pam_regex/pam_regex.c: Fix typo.
+
+ * pam_ldaphome/Makefile.am: New file.
+
2011-09-29 Sergey Poznyakoff <gray@gnu.org.ua>
Allow installers to link modules with alternative crypt(3)
diff --git a/Makefile.am b/Makefile.am
index d8e3efa..fb8be68 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,4 +14,24 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
AUTOMAKE_OPTIONS = gnits 1.8
-SUBDIRS = doc lib pam_fshadow pam_regex pam_log pam_sql pamck
+if PAM_COND_FSHADOW
+ FSHADOW_DIR=pam_fshadow
+endif
+if PAM_COND_REGEX
+ REGEX_DIR=pam_regex
+endif
+if PAM_COND_LOG
+ LOG_DIR=pam_regex
+endif
+if PAM_COND_LDAPHOME
+ LDAPHOME_DIR=pam_ldaphome
+endif
+SUBDIRS = \
+ doc\
+ lib\
+ $(FSHADOW_DIR)\
+ $(REGEX_DIR)\
+ $(LOG_DIR)\
+ pam_sql\
+ $(LDAPHOME_DIR)\
+ pamck
diff --git a/acinclude.m4 b/acinclude.m4
index fa629dd..1ec43dd 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -1,4 +1,4 @@
-# Copyright (C) 2001, 2006, 2008-2011 Sergey Poznyakoff
+# Copyright (C) 2001, 2006, 2008-2012 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
@@ -18,13 +18,17 @@ AC_DEFUN([PM_ENABLE],[
AC_HELP_STRING([--disable-$1], [Disable pam_$1]),
[if test $build_$1 = probe; then
build_$1=$enableval
- fi])
+ fi],
+ [build_$1=probe])
+ m4_pushdef([upmodname],translit($1, [a-z.-], [A-Z__]))
if test $build_$1 != no; then
build_$1=yes
- m4_pushdef([upmodname],[PAM_]translit($1, [a-z.-], [A-Z__]))
- AC_SUBST([BUILD_]upmodname, ['$(]upmodname[)'])
+ AC_SUBST([BUILD_PAM_]upmodname, ['$(]upmodname[)'])
$2
- fi])
+ fi
+ AM_CONDITIONAL([PAM_COND_]upmodname, [test "$[]build_$1" = "yes"])
+ m4_popdef([upmodname])
+])
dnl PM_FLUSHLEFT -- remove all whitespace at the beginning of lines
dnl This is useful for c-code which may include cpp statements
diff --git a/configure.ac b/configure.ac
index 3cff92c..737f191 100644
--- a/configure.ac
+++ b/configure.ac
@@ -30,12 +30,6 @@ AC_SUBST(VI_CURRENT,1)
AC_SUBST(VI_REVISION,0)
AC_SUBST(VI_AGE,0)
-dnl Modules
-build_fshadow=probe
-build_regex=probe
-build_log=probe
-build_pgsql=probe
-
dnl Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
@@ -153,6 +147,14 @@ AC_SUBST(MYSQLLIBS)
AC_SUBST(PGSQLLIBS)
AC_SUBST(SQL_MODULES)
+# LDAP support
+PM_ENABLE(ldaphome)
+if test $build_ldaphome = probe; then
+ AC_CHECK_LIB(ldap, ldap_bind,
+ [build_ldaphome=yes]
+ [build_ldaphome=no])
+fi
+
## *****************
## debugging support
## *****************
@@ -196,7 +198,7 @@ delim="-------------------------------------------------------------------"
echo $delim | tr '-' '*'
echo "Modules to build:"
res=
-for module in fshadow regex log pgsql mysql
+for module in fshadow regex log pgsql mysql ldaphome
do
modname=pam_$module
eval enable=\${build_$module}
@@ -216,7 +218,8 @@ build_fshadow=$build_fshadow
build_regex=$build_regex
build_log=$build_log
build_pgsql=$build_pgsql
-build_mysql=$build_mysql])
+build_mysql=$build_mysql
+build_ldaphome=$build_ldaphome])
AC_OUTPUT(Makefile
doc/Makefile
@@ -225,4 +228,5 @@ AC_OUTPUT(Makefile
pam_regex/Makefile
pam_log/Makefile
pam_sql/Makefile
+ pam_ldaphome/Makefile
pamck/Makefile)
diff --git a/lib/Makefile.am b/lib/Makefile.am
index ad4ca9d..8652898 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -16,6 +16,7 @@
lib_LTLIBRARIES = libgraypam.la
libgraypam_la_SOURCES = \
+ env.c\
log.c\
mem.c\
slist.c\
diff --git a/lib/env.c b/lib/env.c
new file mode 100644
index 0000000..d62a97b
--- /dev/null
+++ b/lib/env.c
@@ -0,0 +1,168 @@
+/* This file is part of pam-modules.
+ Copyright (C) 2008, 2010-2012 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+#include <graypam.h>
+
+char *
+gray_env_get(struct gray_env *env, const char *name)
+{
+ for (; env; env = env->next)
+ if (strcmp(env->name, name) == 0)
+ return env->value;
+ return NULL;
+}
+
+void
+gray_env_free(struct gray_env *env)
+{
+ while (env) {
+ struct gray_env *next = env->next;
+ free(env->name);
+ free(env);
+ env = next;
+ }
+}
+
+int
+gray_env_read(const char *file_name, struct gray_env **penv)
+{
+ FILE *fp;
+ char *p;
+ int rc = 0;
+ int line = 0;
+ char buf[128];
+ gray_slist_t slist = NULL;
+ struct gray_env *config_env = NULL;
+
+ fp = fopen(file_name, "r");
+ if (!fp) {
+ _pam_log(LOG_ERR, "cannot open configuration file `%s': %s",
+ file_name, strerror (errno));
+ return 1;
+ }
+
+ config_env = NULL;
+ while (p = fgets(buf, sizeof buf, fp)) {
+ int len;
+ struct gray_env *env;
+
+ line++;
+ while (*p && isspace(*p))
+ p++;
+ len = strlen(p);
+ if (len == 0)
+ continue;
+ if (p[len-1] != '\n') {
+ if (!slist)
+ slist = gray_slist_create();
+ gray_slist_append(slist, p, len);
+ while (p = fgets(buf, sizeof buf, fp)) {
+ len = strlen(p);
+ gray_slist_append(slist, p, len);
+ if (p[len - 1] == '\n')
+ break;
+ }
+ gray_slist_append_char(slist, 0);
+ p = gray_slist_finish(slist);
+ len = strlen(p);
+ }
+
+ p[len-1] = 0;
+ len = gray_trim_ws(p);
+
+ if (*p == 0 || *p == '#')
+ continue;
+
+ if (p[len-1] == '\\') {
+ int err = 0;
+
+ /* Collect continuation lines */
+ if (!slist)
+ slist = gray_slist_create();
+ do {
+ gray_slist_append(slist, p, len - 1);
+ p = fgets (buf, sizeof buf, fp);
+ if (!p)
+ break;
+ line++;
+ len = strlen(p);
+ if (len == 0)
+ break;
+ if (p[len-1] != '\n') {
+ _pam_log(LOG_EMERG,
+ "%s:%d: string too long",
+ file_name, line);
+ err = 1;
+ break;
+ }
+ p[len-1] = 0;
+ len = gray_trim_ws(p);
+ } while (p[len-1] == '\\');
+ if (len)
+ gray_slist_append(slist, p, len);
+ gray_slist_append_char(slist, 0);
+ p = gray_slist_finish(slist);
+ if (err)
+ 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++)
+ ;
+ if (*p)
+ *p++ = 0;
+ for (; *p && isspace(*p); p++)
+ ;
+ if (!*p) {
+ _pam_log(LOG_EMERG, "%s:%d: not enough fields",
+ file_name, line);
+ free(env->name);
+ free(env);
+ continue;
+ }
+ env->value = p;
+ env->next = config_env;
+ config_env = env;
+ }
+
+ gray_slist_free(&slist);
+ fclose(fp);
+ *penv = config_env;
+ return rc;
+}
+
+int
+gray_boolean_true_p(const char *value)
+{
+ return strcmp(value, "yes") == 0
+ || strcmp(value, "true") == 0
+ || strcmp(value, "t") == 0;
+}
+
diff --git a/lib/graypam.h b/lib/graypam.h
index 8061a1b..5dbdbd4 100644
--- a/lib/graypam.h
+++ b/lib/graypam.h
@@ -196,5 +196,19 @@ struct keyword *gray_find_keyword(struct keyword *kwtab, const char *str,
int gray_trim_ws(char *str);
+
+/* configuration file support */
+
+struct gray_env {
+ struct gray_env *next;
+ char *name;
+ char *value;
+};
+
+char *gray_env_get(struct gray_env *env, const char *name);
+void gray_env_free(struct gray_env *env);
+int gray_env_read(const char *file_name, struct gray_env **penv);
+
+int gray_boolean_true_p(const char *value);
#endif
diff --git a/pam_fshadow/Makefile.am b/pam_fshadow/Makefile.am
index f58b87d..3555568 100644
--- a/pam_fshadow/Makefile.am
+++ b/pam_fshadow/Makefile.am
@@ -14,9 +14,7 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
pamdir=@PAMDIR@
-PAM_FSHADOW = pam_fshadow.la
-pam_LTLIBRARIES = @BUILD_PAM_FSHADOW@
-EXTRA_LTLIBRARIES = pam_fshadow.la
+pam_LTLIBRARIES = pam_fshadow.la
pam_fshadow_la_SOURCES = pam_fshadow.c
AM_CPPFLAGS=-DMODULE_NAME=\"pam_fshadow\" -DSYSCONFDIR=\"${sysconfdir}\"
diff --git a/pam_ldaphome/Makefile.am b/pam_ldaphome/Makefile.am
new file mode 100644
index 0000000..ae46a08
--- /dev/null
+++ b/pam_ldaphome/Makefile.am
@@ -0,0 +1,23 @@
+# This file is part of pam-modules.
+# Copyright (C) 2012 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+include $(top_srcdir)/Make.rules
+
+pamdir=@PAMDIR@
+pam_LTLIBRARIES = pam_ldaphome.la
+AM_CPPFLAGS=-DMODULE_NAME=\"pam_ldaphome\" -DSYSCONFDIR=\"${sysconfdir}\"
+pam_ldaphome_la_SOURCES = pam_ldaphome.c
+pam_ldaphome_la_LIBADD = @PAM_MISC@ -lldap
diff --git a/pam_ldaphome/pam_ldaphome.c b/pam_ldaphome/pam_ldaphome.c
new file mode 100644
index 0000000..bee64a2
--- /dev/null
+++ b/pam_ldaphome/pam_ldaphome.c
@@ -0,0 +1,865 @@
+/* This file is part of pam-modules.
+ Copyright (C) 2005-2008, 2010-2011 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE__PAM_ACONF_H
+# include <security/_pam_aconf.h>
+#endif
+#ifndef LINUX_PAM
+# include <security/pam_appl.h>
+#endif /* LINUX_PAM */
+#include <security/pam_modules.h>
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ldap.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "graypam.h"
+
+/* indicate the following groups are defined */
+#define PAM_SM_AUTH
+
+static long debug_level;
+static int cntl_flags;
+static char *config_file_name;
+static int ldap_debug_level;
+/* FIXME: This should be read from sshd_config */
+static char *authorized_keys_file=".ssh/authorized_keys";
+
+struct pam_opt pam_opt[] = {
+ { PAM_OPTSTR(debug), pam_opt_long, &debug_level },
+ { PAM_OPTSTR(debug), pam_opt_const, &debug_level, { 1 } },
+ { PAM_OPTSTR(audit), pam_opt_bitmask, &cntl_flags, { CNTL_AUDIT } },
+ { PAM_OPTSTR(waitdebug), pam_opt_null, NULL, { 0 },
+ gray_wait_debug_fun },
+ { PAM_OPTSTR(config), pam_opt_string, &config_file_name },
+ { NULL }
+};
+
+static void
+_pam_parse(pam_handle_t *pamh, int argc, const char **argv)
+{
+ cntl_flags = 0;
+ debug_level = 0;
+ config_file_name = SYSCONFDIR "/" MODULE_NAME ".conf";
+ gray_log_init(0, MODULE_NAME, LOG_AUTHPRIV);
+ gray_parseopt(pam_opt, argc, argv);
+}
+
+static void
+argcv_free(int wc, char **wv)
+{
+ int i;
+
+ for (i = 0; i < wc; i++) {
+ free(wv[i]);
+ free(wv);
+ }
+}
+
+static int
+argcv_split(const char *str, int *pargc, char ***pargv)
+{
+ int argc, i;
+ char **argv;
+ const char *p;
+ int rc = 0;
+
+ argc = 1;
+ for (p = str; *p; p++) {
+ if (*p == ' ')
+ argc++;
+ }
+ argv = calloc(argc + 1, sizeof(argv[0]));
+ if (!argv)
+ return 1;
+ for (i = 0, p = str;;) {
+ size_t len = strcspn(p, " ");
+ char *q = malloc(len + 1);
+
+ if (!q) {
+ rc = errno;
+ break;
+ }
+ memcpy(q, p, len);
+ q[len] = 0;
+ argv[i++] = q;
+ p += len;
+ if (p)
+ p += strspn(p, " ");
+ if (!*p)
+ break;
+ }
+
+ if (rc) {
+ argcv_free(argc, argv);
+ errno = rc;
+ return 1;
+ }
+
+ argv[i] = NULL;
+ *pargc = argc;
+ *pargv = argv;
+ return 0;
+}
+
+static char *
+argcv_concat(int wc, char **wv)
+{
+ char *res, *p;
+ size_t size = 0;
+ int i;
+
+ for (i = 0; i < wc; i++)
+ size += strlen(wv[i]) + 1;
+ size++;
+ res = malloc(size);
+ if (!res)
+ return 0;
+ for (p = res, i = 0; ; i++) {
+ strcpy(p, wv[i]);
+ p += strlen(wv[i]);
+ if (i < wc)
+ *p++ = ' ';
+ else
+ break;
+ }
+ *p = 0;
+ return res;
+}
+
+char *
+parse_ldap_uri(const char *uri)
+{
+ int wc;
+ char **wv;
+ LDAPURLDesc *ludlist, **ludp;
+ char **urls = NULL;
+ int nurls = 0;
+ char *ldapuri = NULL;
+ int rc;
+
+ rc = ldap_url_parse(uri, &ludlist);
+ if (rc != LDAP_URL_SUCCESS) {
+ _pam_log(LOG_ERR, "cannot parse LDAP URL(s)=%s (%d)",
+ uri, rc);
+ return NULL;
+ }
+
+ for (ludp = &ludlist; *ludp; ) {
+ LDAPURLDesc *lud = *ludp;
+ char **tmp;
+
+ if (lud->lud_dn && lud->lud_dn[0]
+ && (lud->lud_host == NULL || lud->lud_host[0] == '\0')) {
+ /* if no host but a DN is provided, try
+ DNS SRV to gather the host list */
+ char *domain = NULL, *hostlist = NULL;
+ size_t i;
+
+ if (ldap_dn2domain (lud->lud_dn, &domain) ||
+ !domain) {
+ _pam_log(LOG_ERR,
+ "DNS SRV: cannot convert "
+ "DN=\"%s\" into a domain",
+ lud->lud_dn);
+ goto dnssrv_free;
+ }
+
+ rc = ldap_domain2hostlist(domain, &hostlist);
+ if (rc) {
+ _pam_log(LOG_ERR,
+ "DNS SRV: cannot convert "
+ "domain=%s into a hostlist",
+ domain);
+ goto dnssrv_free;
+ }
+
+ if (argcv_split(hostlist, &wc, &wv)) {
+ _pam_log(LOG_ERR,
+ "DNS SRV: could not parse "
+ "hostlist=\"%s\": %s",
+ hostlist, strerror(errno));
+ goto dnssrv_free;
+ }
+
+ tmp = realloc(urls, sizeof(char *) * (nurls + wc + 1));
+ if (!tmp) {
+ _pam_log(LOG_ERR,
+ "DNS SRV %s", strerror(errno));
+ goto dnssrv_free;
+ }
+
+ urls = tmp;
+ urls[nurls] = NULL;
+
+ for (i = 0; i < wc; i++) {
+ char *p = malloc(strlen(lud->lud_scheme) +
+ strlen(wv[i]) +
+ 3);
+ if (!p) {
+ _pam_log(LOG_ERR, "DNS SRV %s",
+ strerror(errno));
+ goto dnssrv_free;
+ }
+
+ strcpy(p, lud->lud_scheme);
+ strcat(p, "//");
+ strcat(p, wv[i]);
+
+ urls[nurls + i + 1] = NULL;
+ urls[nurls + i] = p;
+ }
+
+ nurls += i;
+
+ dnssrv_free:
+ argcv_free(wc, wv);
+ ber_memfree(hostlist);
+ ber_memfree(domain);
+ } else {
+ tmp = realloc(urls, sizeof(char *) * (nurls + 2));
+ if (!tmp) {
+ _pam_log(LOG_ERR,
+ "DNS SRV %s", strerror(errno));
+ break;
+ }
+ urls = tmp;
+ urls[nurls + 1] = NULL;
+
+ urls[nurls] = ldap_url_desc2str(lud);
+ if (!urls[nurls]) {
+ _pam_log(LOG_ERR, "DNS SRV %s",
+ strerror(errno));
+ break;
+ }
+ nurls++;
+ }
+
+ *ludp = lud->lud_next;
+
+ lud->lud_next = NULL;
+ ldap_free_urldesc(lud);
+ }
+
+ if (ludlist) {
+ ldap_free_urldesc (ludlist);
+ return NULL;
+ } else if (!urls)
+ return NULL;
+ ldapuri = argcv_concat(wc, wv);
+ if (!ldapuri)
+ _pam_log(LOG_ERR, "%s", strerror(errno));
+ ber_memvfree ((void **)urls);
+ return ldapuri;
+}
+
+static LDAP *
+ldap_connect(struct gray_env *env)
+{
+ int rc;
+ char *ldapuri = NULL;
+ LDAP *ld = NULL;
+ int protocol = LDAP_VERSION3; /* FIXME: must be configurable */
+ char *val;
+
+ if (ldap_debug_level) {
+ if (ber_set_option (NULL, LBER_OPT_DEBUG_LEVEL,
+ &ldap_debug_level)
+ != LBER_OPT_SUCCESS )
+ _pam_log(LOG_ERR,
+ "cannot set LBER_OPT_DEBUG_LEVEL %d",
+ ldap_debug_level);
+
+ if (ldap_set_option (NULL, LDAP_OPT_DEBUG_LEVEL,
+ &ldap_debug_level)
+ != LDAP_OPT_SUCCESS )
+ _pam_log(LOG_ERR,
+ "could not set LDAP_OPT_DEBUG_LEVEL %d",
+ ldap_debug_level);
+ }
+
+ val = gray_env_get(env, "uri");
+ if (val) {
+ ldapuri = parse_ldap_uri(val);
+ if (!ldapuri)
+ return NULL;
+ }
+ DEBUG(2, ("constructed LDAP URI: %s",
+ ldapuri ? ldapuri : "<DEFAULT>"));
+
+ rc = ldap_initialize(&ld, ldapuri);
+ if (rc != LDAP_SUCCESS) {
+ _pam_log(LOG_ERR,
+ "cannot create LDAP session handle for "
+ "URI=%s (%d): %s",
+ ldapuri, rc, ldap_err2string(rc));
+ free(ldapuri);
+ return NULL;
+ }
+ free(ldapuri);
+
+ val = gray_env_get(env, "tls");
+
+ if (val && gray_boolean_true_p(val)) {
+ rc = ldap_start_tls_s(ld, NULL, NULL);
+ if (rc != LDAP_SUCCESS) {
+ _pam_log(LOG_ERR,
+ "ldap_start_tls failed: %s",
+ ldap_err2string(rc));
+ /* try to continue anyway, to avoid memory
+ leek (ld not being freed) */
+ }
+ }
+
+ ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol);
+
+ /* FIXME: Timeouts, SASL, etc. */
+ return ld;
+}
+
+static int
+ldap_bind (LDAP *ld, struct gray_env *env)
+{
+ int msgid, err, rc;
+ LDAPMessage *result;
+ LDAPControl **ctrls;
+ char msgbuf[256];
+ char *matched = NULL;
+ char *info = NULL;
+ char **refs = NULL;
+ static struct berval passwd;
+ char *binddn;
+
+ binddn = gray_env_get(env, "binddn");
+ passwd.bv_val = gray_env_get(env, "bindpw");
+ passwd.bv_len = passwd.bv_val ? strlen(passwd.bv_val) : 0;
+
+ msgbuf[0] = 0;
+
+ rc = ldap_sasl_bind(ld, binddn, LDAP_SASL_SIMPLE, &passwd,
+ NULL, NULL, &msgid);
+ if (msgid == -1) {
+ _pam_log(LOG_ERR,
+ "ldap_sasl_bind(SIMPLE) failed: %s",
+ ldap_err2string(rc));
+ return 1;
+ }
+
+ if (ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1) {
+ _pam_log(LOG_ERR, "ldap_result failed");
+ return 1;
+ }
+
+ rc = ldap_parse_result(ld, result, &err, &matched, &info, &refs,
+ &ctrls, 1);
+ if (rc != LDAP_SUCCESS) {
+ _pam_log(LOG_ERR, "ldap_parse_result failed: %s",
+ ldap_err2string (rc));
+ return 1;
+ }
+
+ if (ctrls)
+ ldap_controls_free(ctrls);
+
+ if (err != LDAP_SUCCESS
+ || msgbuf[0]
+ || (matched && matched[0])
+ || (info && info[0])
+ || refs) {
+ /* FIXME: Use debug output for that */
+ DEBUG(2,("ldap_bind: %s (%d)%s",
+ ldap_err2string(err), err, msgbuf));
+
+ if (matched && *matched)
+ DEBUG(2,("matched DN: %s", matched));
+
+ if (info && *info)
+ DEBUG(2,("additional info: %s", info));
+
+ if (refs && *refs) {
+ int i;
+ DEBUG(3,("referrals:"));
+ for (i = 0; refs[i]; i++)
+ DEBUG(3,("%s", refs[i]));
+ }
+ }
+
+ if (matched)
+ ber_memfree(matched);
+ if (info)
+ ber_memfree(info);
+ if (refs)
+ ber_memvfree((void **)refs);
+
+ return !(err == LDAP_SUCCESS);
+}
+
+static void
+ldap_unbind(LDAP *ld)
+{
+ if (ld) {
+ ldap_set_option(ld, LDAP_OPT_SERVER_CONTROLS, NULL);
+ ldap_unbind_ext(ld, NULL, NULL);
+ }
+}
+
+static char *
+get_ldap_attr(LDAP *ld, LDAPMessage *msg, const char *attr)
+{
+ int rc;
+ BerElement *ber = NULL;
+ struct berval bv;
+ char *ufn = NULL;
+ char *val;
+ struct berval **values;
+
+ rc = ldap_get_dn_ber(ld, msg, &ber, &bv);
+ ufn = ldap_dn2ufn(bv.bv_val);
+ DEBUG(2, ("INFO: %s", ufn));
+ ldap_memfree(ufn);
+
+ values = ldap_get_values_len(ld, msg, attr);
+ if (!values || !values[0]) {
+ _pam_log(LOG_ERR,
+ "LDAP attribute `%s' has NULL value",
+ attr);
+ return NULL;
+ }
+ val = strdup(values[0]->bv_val);
+ if (!val)
+ _pam_log(LOG_ERR, "%s", strerror(errno));
+ else
+ DEBUG(1, ("pubkey: %s", val));
+ ldap_value_free_len(values);
+ return val;
+}
+
+static char *
+ldap_search(LDAP *ld, const char *base, const char *filter, const char *attr)
+{
+ int rc;
+ LDAPMessage *res, *msg;
+ ber_int_t msgid;
+ char *attrs[2];
+ char *ret;
+
+ attrs[0] = (char*) attr;
+ attrs[1] = NULL;
+ rc = ldap_search_ext(ld, base, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0,
+ NULL, NULL, NULL, -1, &msgid);
+
+ if (rc != LDAP_SUCCESS) {
+ _pam_log(LOG_ERR, "ldap_search_ext: %s", ldap_err2string(rc));
+ return NULL;
+ }
+
+ rc = ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &res);
+ if (rc < 0) {
+ _pam_log(LOG_ERR, "ldap_result failed");
+ return NULL;
+ }
+
+ rc = ldap_count_entries(ld, res);
+ if (rc == 0) {
+ _pam_log(LOG_ERR, "not enough entires");
+ return NULL;
+ }
+ if (rc > 1)
+ _pam_log(LOG_NOTICE, "LDAP: too many entries for filter %s",
+ filter);
+
+ msg = ldap_first_entry(ld, res);
+ ret = get_ldap_attr(ld, msg, attr);
+ ldap_msgfree(res);
+
+ return ret;
+}
+
+static int
+get_intval(struct gray_env *env, const char *name, unsigned long *pv)
+{
+ char *p;
+ char *v = gray_env_get(env, name);
+
+ if (!v)
+ return 1;
+ *pv = strtoul(v, &p, 10);
+ if (*p) {
+ _pam_log(LOG_ERR, "configuration variable %s is not integer",
+ name);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+check_groups(int gc, char **gv, const char *username)
+{
+ int i;
+
+ for (i = 0; i < gc; i++) {
+ struct group *gp = getgrnam(gv[i]);
+ if (gp) {
+ char **p;
+ for (p = gp->gr_mem; *p; p++)
+ if (strcmp(username, *p) == 0)
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+check_user_groups(pam_handle_t *pamh, struct gray_env *env,
+ struct passwd **ppw, int *retval)
+{
+ int rc;
+ const char *username;
+ struct passwd *pw;
+ unsigned long ival;
+ char *sval;
+
+ rc = pam_get_user(pamh, &username, NULL);
+ if (rc != PAM_SUCCESS || !username) {
+ DEBUG(1,("can not get the username"));
+ *retval = rc;
+ return 1;
+ }
+ pw = getpwnam(username);
+ if (!pw) {
+ *retval = PAM_USER_UNKNOWN;
+ return 1;
+ }
+ *ppw = pw;
+ if (get_intval(env, "min-uid", &ival) == 0) {
+ if (pw->pw_uid < ival) {
+ DEBUG(10, ("ignoring user %s: has UID < %lu",
+ username, ival));
+ *retval = PAM_SUCCESS;
+ return 1;
+ }
+ }
+ if (get_intval(env, "min-gid", &ival) == 0) {
+ if (pw->pw_gid < ival) {
+ DEBUG(10, ("ignoring user %s: has GID < %lu",
+ username, ival));
+ *retval = PAM_SUCCESS;
+ return 1;
+ }
+ }
+ sval = gray_env_get(env, "allow-groups");
+ if (sval) {
+ int gc;
+ char **gv;
+ int rc;
+
+ if (argcv_split(sval, &gc, &gv)) {
+ _pam_log(LOG_ERR, "cannot split allow-groups: %s",
+ strerror(errno));
+ *retval = PAM_AUTH_ERR;
+ return 1;
+ }
+ rc = check_groups(gc, gv, username);
+ argcv_free(gc, gv);
+ if (rc) {
+ DEBUG(10, ("ignoring user %s: not in allowed group list",
+ username, ival));
+ *retval = PAM_SUCCESS;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+populate_homedir(struct passwd *pw, const char *skel)
+{
+ // FIXME!!!
+ _pam_log(LOG_ERR, "populate_homedir is not yet implemented!");
+ return 1;
+}
+
+/* Create the directory DIR, eventually creating all intermediate directories
+ starting from DIR + BASELEN. */
+int
+create_hierarchy(char *dir, size_t baselen)
+{
+ int rc;
+ struct stat st;
+ char *p;
+
+ if (stat(dir, &st) == 0) {
+ if (!S_ISDIR(st.st_mode)) {
+ _pam_log(LOG_ERR, "component %s is not a directory",
+ dir);
+ return 1;
+ }
+ return 0;
+ } else if (errno != ENOENT) {
+ _pam_log(LOG_ERR, "cannot stat file %s: %s",
+ dir, strerror(errno));
+ return 1;
+ }
+
+ p = strrchr(dir, '/');
+ if (p) {
+ if (p - dir + 1 < baselen) {
+ _pam_log(LOG_ERR, "base directory %s does not exist",
+ dir);
+ return 1;
+ }
+ *p = 0;
+ }
+
+ rc = create_hierarchy(dir, baselen);
+ if (rc == 0) {
+ if (p)
+ *p = '/';
+ if (mkdir(dir, 0755)) {
+ _pam_log(LOG_ERR, "cannot create directory %s: %s",
+ dir, strerror(errno));
+ rc = 1;
+ }
+ }
+ return rc;
+}
+
+int
+create_interdir(const char *path, struct passwd *pw)
+{
+ char *dir, *p;
+ size_t len;
+ int rc;
+
+ p = strrchr(path, '/');
+ if (!p)
+ return 1;
+ len = p - path;
+ dir = gray_malloc(len + 1);
+ memcpy(dir, path, len);
+ dir[len] = 0;
+ rc = create_hierarchy(dir, strlen(pw->pw_dir));
+ if (rc == 0)
+ chown(dir, pw->pw_uid, pw->pw_gid);
+ free(dir);
+ return rc;
+}
+
+static void
+store_pubkey(const char *key, struct passwd *pw)
+{
+ FILE *fp;
+ const char *kp;
+ int c;
+ int found = 0;
+ char *file_name;
+ size_t homelen, pathlen, len;
+
+ homelen = strlen(pw->pw_dir);
+ pathlen = strlen(authorized_keys_file);
+ len = homelen + pathlen;
+ if (pw->pw_dir[homelen - 1] != '/')
+ len++;
+ file_name = gray_malloc(len + 1);
+ memcpy(file_name, pw->pw_dir, homelen);
+ if (pw->pw_dir[homelen - 1] != '/')
+ file_name[homelen++] = '/';
+ strcpy(file_name + homelen, authorized_keys_file);
+
+ fp = fopen(file_name, "a+");
+ if (!fp && create_interdir(file_name, pw) == 0)
+ fp = fopen(file_name, "a+");
+ if (!fp) {
+ _pam_log(LOG_EMERG, "cannot open file %s: %s",
+ file_name, strerror(errno));
+ free(file_name);
+ return;
+ }
+ free(file_name);
+ fchown(fileno(fp), pw->pw_uid, pw->pw_gid);
+
+ kp = key;
+ while (!feof(fp)) {
+ while (*kp && (c = getc(fp)) != EOF && c == *kp)
+ kp++;
+ if (*kp == 0) {
+ DEBUG(2, ("key found"));
+ found = 1;
+ break;
+ }
+ kp = key;
+ if (c != '\n') {
+ if (c != EOF) {
+ while ((c = getc(fp)) != EOF && c != '\n')
+ ;
+ }
+ if (c == EOF) {
+ if (ftell(fp))
+ fputc('\n', fp);
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ fwrite(key, strlen(key), 1, fp);
+ fputc('\n', fp);
+ }
+ fclose(fp);
+}
+
+static int
+import_public_key(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env)
+{
+ LDAP *ld;
+ int retval;
<