diff options
-rw-r--r-- | Makefile.am | 5 | ||||
-rwxr-xr-x | examples/usergitconfig | 195 | ||||
-rw-r--r-- | pam_ldaphome/pam_ldaphome.c | 160 |
3 files changed, 359 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am index e72f2c3..7a55296 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,7 +48,10 @@ SUBDIRS = \ $(GROUPMEMBER_DIR)\ pamck -EXTRA_DIST=ChangeLog.svn +EXTRA_DIST=ChangeLog.svn examples +dist-hook: + rm -rf `find $(distdir)/examples -name '*~'` + # Name of the previous ChangeLog file. prev_change_log = ChangeLog.svn # Start Git ChangeLog from this date. diff --git a/examples/usergitconfig b/examples/usergitconfig new file mode 100755 index 0000000..ad2ab5e --- /dev/null +++ b/examples/usergitconfig @@ -0,0 +1,195 @@ +#! /usr/bin/perl +# This file is part of pam-modules. +# Copyright (C) 2014 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, 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/>. + +use strict; +use Net::LDAP; + +=head1 NAME + +usergitconfig - initialize user .gitconfig file + +=head1 SYNOPSIS + +B<usergitconfig> I<LOGIN> + +=head1 DESCRIPTION + +This program reads the B<.gitconfig> file from the current working directory +and expands occurrences of B<${I<ATTRIBUTE>}> with the value of I<ATTRIBUTE> +from the LDAP B<posixAccount> record for user I<LOGIN>. If there is no such +attribute, an empty value is substituted. + +The program reads its configuration from one of the following files: +B</etc/ldap.conf>, B</etc/ldap/ldap.conf> and B</etc/openldap/ldap.conf>. +These files are tried in this order and the first one of them that exists +is read. + +The following configuration statements are used: + +=over 4 + +=item B<uri> B<ldap[si]://>[I<name>[:I<port>]] ...> + +Specifies the URI of the LDAP server (or servers) to connect to. The default +is B<ldap://127.0.0.1>. + +=item B<base> I<DN> + +Specifies the default base DN to use when performing ldap operations. +The base must be specified as a Distinguished Name in LDAP format. + +=item B<binddn> I<DN> + +Specifies the default bind DN to use. + +=item B<binddnpw> I<PASS> + +Specifies the password to use with B<binddn>. + +=item B<uid> I<ATTR> + +Name of the attribute to use instead of B<uid>. The LDAP record is searched +using the filter B<(&(objectClass=posixAccount)(I<ATTR>=I<LOGIN>))>. + +=back + +All keywords are case-insensitive. + +By default, the program expands the keywords in the B<.gitconfig> file. This +can be changed via the environment variable B<GITCONFIG_TEMPLATE>. If it is +set, the program uses its value as the name of the file to use. This file is +read line by line, expanding the keywords encountered and the resulting text +is written to B<.gitconfig>. + +The program is intended to be used in B<initrc-command> keyword of the +B<pam_ldaphome>(8) configuration file. + +=head1 SEE ALSO + +B<pam_ldaphome>(8), B<ldap.conf>(5). + +=head1 AUTHOR + +Sergey Poznyakoff <gray@gnu.org> + +=cut + +# ################################### +# Configuration file handling +# ################################### + +my %config = ('uri' => 'ldap://127.0.0.1', 'uid' => 'uid'); + +sub read_config_file($) { + my $config_file = shift; + my $file; + my $line = 0; + + open($file, "<", $config_file) or die("cannot open $config_file: $!"); + while (<$file>) { + ++$line; + chomp; + s/^\s+//; + s/\s+$//; + s/#.*//; + next if ($_ eq ""); + my @kwp = split(/\s*\s+\s*/, $_, 2); + $config{lc($kwp[0])} = $kwp[1]; + } + close($file); +} + +# ################################### +# LDAP Functions +# ################################### + +sub assert { + my $mesg = shift; + my $action = shift; + die("An error occurred $action: ".$mesg->error) if ($mesg->code); + return $mesg; +} + +sub ldap_connect { + my $ldap = Net::LDAP->new($config{'uri'}) + or die("Unable to connect to LDAP server $config{'uri'}: $!"); + my @bindargs = (); + if (defined($config{'binddn'})) { + push(@bindargs, $config{'binddn'}); + push(@bindargs, password => $config{'bindpass'}) # FIXME + if defined($config{'bindpass'}); + } + assert($ldap->bind(@bindargs), "binding to the server"); + return $ldap; +} + +# ################################### +# MAIN +# ################################### + +die "bad number of arguments" unless ($#ARGV == 0); + +## Read configuration +foreach my $file ("/etc/ldap.conf", "/etc/ldap/ldap.conf", + "/etc/openldap/ldap.conf") { + if (-e $file) { + read_config_file($file); + last; + } +} + +my $ldap = ldap_connect; + +my $filter = "(&(objectClass=posixAccount)($config{'uid'}=$ARGV[0]))"; + +my $res = assert($ldap->search(base => $config{'base'}, + filter => $filter), + "searching for $filter in $config{'base'}"); +my $entry = $res->entry(0); + +my $template = defined($ENV{'GITCONFIG_TEMPLATE'}) + ? $ENV{'GITCONFIG_TEMPLATE'} + : ".gitconfig"; +open(my $fd, "<", $template) or die "can't open $template: $!"; + +my $outfile; +if ($template eq ".gitconfig") { + $outfile = ".gitconfig.$$"; +} else { + $outfile = ".gitconfig"; +} + +open(my $ofd, ">", $outfile) or die "can't open $outfile for output: $!"; + +while (<$fd>) { + s/\$\{(.*?)\}/$entry->get_value($1)/gex; + print $ofd $_; +} + +close $fd; +close $ofd; + +if ($outfile ne ".gitconfig") { + unlink(".gitconfig") + or die "can't unlink .gitconfig: $1"; + rename($outfile, ".gitconfig") + or die "can't rename $outfile to .gitconfig: $1"; +} + +$ldap->unbind; + + diff --git a/pam_ldaphome/pam_ldaphome.c b/pam_ldaphome/pam_ldaphome.c index b83063a..f3191b9 100644 --- a/pam_ldaphome/pam_ldaphome.c +++ b/pam_ldaphome/pam_ldaphome.c @@ -30,6 +30,9 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <time.h> +#include <sys/time.h> +#include <sys/wait.h> #include <ldap.h> #include <pwd.h> #include <grp.h> @@ -1429,6 +1432,160 @@ create_home_dir(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env) return create_ok; } +static int +run_prog(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env, + const char *command, const char *logfile) +{ + pid_t pid, rc; + int p[2]; + long ttl; + time_t start; + int i, status; + struct timeval tv; + unsigned long timeout_option = 10; + + DEBUG(2,("running command %s", command)); + get_intval(env, "exec-timeout", 10, &timeout_option); + + if (pipe(p)) { + _pam_log(LOG_ERR, "pipe: %s", strerror(errno)); + return PAM_SYSTEM_ERR; + } + + pid = fork(); + if (pid == -1) { + close(p[0]); + close(p[1]); + _pam_log(LOG_ERR, "fork: %s", strerror(errno)); + return PAM_SYSTEM_ERR; + } + + if (pid == 0) { + /* child */ + char *argv[3]; + + if (chdir(pw->pw_dir)) { + _pam_log(LOG_ERR, "chdir: %s", strerror(errno)); + _exit(127); + } + + if (dup2(p[1], 1) == -1) { + _pam_log(LOG_ERR, "dup2: %s", strerror(errno)); + _exit(127); + } + for (i = sysconf(_SC_OPEN_MAX); i >= 0; i--) { + if (i != 1) + close(i); + } + open("/dev/null", O_RDONLY); + if (logfile) { + if (open(logfile, O_CREAT|O_APPEND|O_WRONLY, + 0644) == -1) { + _pam_log(LOG_ERR, "open(%s): %s", + logfile, strerror(errno)); + _exit(127); + } + } else + dup2(1, 2); + argv[0] = (char*) command; + argv[1] = pw->pw_name; + argv[2] = NULL; + execv(command, argv); + _exit(127); + } + + /* master */ + close(p[1]); + + start = time(NULL); + while (1) { + ttl = timeout_option - (time(NULL) - start); + if (ttl <= 0) { + _pam_log(LOG_ERR, "timed out waiting for %s", command); + break; + } + tv.tv_sec = ttl; + tv.tv_usec = 0; + rc = select(0, NULL, NULL, NULL, &tv); + if (rc == -1 && errno == EINTR) { + rc = waitpid(pid, &status, WNOHANG); + if (rc == pid) + break; + if (rc == (pid_t)-1) { + _pam_log(LOG_ERR, "waitpid: %s", + strerror(errno)); + break; + } + } + } + + close(p[0]); + + if (rc != pid) { + _pam_log(LOG_NOTICE, "killing %s (pid %lu)", + command, (unsigned long) pid); + kill(pid, SIGKILL); + + while ((rc = waitpid(pid, &status, 0)) == -1 && + errno == EINTR); + if (rc == (pid_t)-1) { + _pam_log(LOG_ERR, "waitpid: %s", strerror(errno)); + return PAM_SYSTEM_ERR; + } + } else if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + if (status) { + _pam_log(LOG_ERR, "%s exited with status %d", + command, status); + return PAM_SYSTEM_ERR; + } else + DEBUG(2,("%s finished successfully", command)); + } else if (WIFSIGNALED(status)) { + status = WTERMSIG(status); + _pam_log(LOG_ERR, "%s got signal %d", command, status); + return PAM_SYSTEM_ERR; + } else if (status) { + _pam_log(LOG_ERR, "%s failed: unknown status 0x%x", + command, status); + return PAM_SYSTEM_ERR; + } + return PAM_SUCCESS; +} + +void +sigchld(int sig) +{ + /* nothing */; +} + +static int +run_initrc(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env) +{ + int rc; + struct sigaction sa, save_sa; + const char *command = gray_env_get(env, "initrc-command"); + const char *logfile = gray_env_get(env, "initrc-log"); + + if (!command) + return PAM_SUCCESS; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sigchld; + if (sigaction(SIGCHLD, &sa, &save_sa)) { + _pam_log(LOG_ERR, "sigaction: %m"); + return PAM_SYSTEM_ERR; + } + + rc = run_prog(pamh, pw, env, command, logfile); + + if (sigaction(SIGCHLD, &save_sa, NULL)) { + _pam_log(LOG_ERR, "sigaction failed to restore SIGCHLD: %m"); + return PAM_SYSTEM_ERR; + } + return rc; +} + PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { @@ -1449,6 +1606,9 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) if (check_user_groups(pamh, env, &pw, &retval) == 0) { switch (create_home_dir(pamh, pw, env)) { case create_ok: + retval = run_initrc(pamh, pw, env); + if (retval) + break; retval = import_public_key(pamh, pw, env); break; case create_failure: |