diff options
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | debian/control | 4 | ||||
-rw-r--r-- | debian/radius | 3 | ||||
-rwxr-xr-x | debian/rules | 2 | ||||
-rw-r--r-- | pam_radius_auth.5 | 22 | ||||
-rw-r--r-- | pam_radius_auth.conf | 16 | ||||
-rw-r--r-- | src/Makefile | 5 | ||||
-rw-r--r-- | src/pam_radius_auth.c | 1078 | ||||
-rw-r--r-- | src/pam_radius_auth.h | 25 | ||||
-rw-r--r-- | src/radius.h | 101 | ||||
-rw-r--r-- | src/support.c | 258 |
11 files changed, 982 insertions, 539 deletions
@@ -15,7 +15,7 @@ VERSION=1.4.1 # If you're not using GCC, then you'll have to change the CFLAGS. # # structured this way instead of += so configured CFLAGS can override -Wall -CFLAGS := -Wall -fPIC ${CFLAGS} +CFLAGS := -Wall -Werror -fPIC ${CFLAGS} # # On Irix, use this with MIPSPRo C Compiler, and don't forget to export CC=cc @@ -39,6 +39,9 @@ all: pam_radius_auth.so # export CFLAGS +src/support.o: src/support.c src/pam_radius_auth.h + @$(MAKE) -C src $(notdir $@) + src/pam_radius_auth.o: src/pam_radius_auth.c src/pam_radius_auth.h @$(MAKE) -C src $(notdir $@) @@ -63,7 +66,7 @@ src/md5.o: src/md5.c src/md5.h # # gcc -shared pam_radius_auth.o md5.o -lpam -lc -o pam_radius_auth.so # -pam_radius_auth.so: src/pam_radius_auth.o src/md5.o +pam_radius_auth.so: src/pam_radius_auth.o src/support.o src/md5.o $(CC) $(LDFLAGS) $^ -lpam -o pam_radius_auth.so ###################################################################### diff --git a/debian/control b/debian/control index cb8cb49..b70c948 100644 --- a/debian/control +++ b/debian/control @@ -3,11 +3,11 @@ Maintainer: dev-support <dev-support@cumulusnetworks.com> Section: libs Priority: extra Standards-Version: 3.9.6 -Build-Depends: libpam0g-dev | libpam-dev, debhelper (>= 9~) +Build-Depends: libpam0g-dev | libpam-dev, debhelper (>= 9~), libaudit-dev Package: libpam-radius-auth Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${shlibs:Depends}, ${misc:Depends}, libaudit1 Description: PAM RADIUS client authentication module This is the PAM to RADIUS authentication module. It allows any PAM-capable machine to become a RADIUS client for authentication and accounting diff --git a/debian/radius b/debian/radius index 5389ed1..7e318a2 100644 --- a/debian/radius +++ b/debian/radius @@ -3,12 +3,15 @@ Default: yes Priority: 257 Auth-Type: Primary Auth: + [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet [authinfo_unavail=ignore success=end default=ignore] pam_radius_auth.so Account-Type: Primary Account: + [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_radius_auth.so Session-Type: Additional Session: + [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet [authinfo_unavail=ignore success=ok default=ignore] pam_radius_auth.so diff --git a/debian/rules b/debian/rules index 9a0976d..52172f8 100755 --- a/debian/rules +++ b/debian/rules @@ -16,7 +16,7 @@ endif export CFLAGS %: - dh $@ + dh $@ --with autoreconf # all the installing is here, not in Makefile. # The configuration file with the share secrets needs to be 600 diff --git a/pam_radius_auth.5 b/pam_radius_auth.5 index 2d25ddf..05a9f0b 100644 --- a/pam_radius_auth.5 +++ b/pam_radius_auth.5 @@ -1,5 +1,5 @@ .TH pam_radius_auth 5 -.\" Copyright 2017 Cumulus Networks, Inc. All rights reserved. +.\" Copyright 2017, 2018 Cumulus Networks, Inc. All rights reserved. .SH NAME pam_radius_auth.conf \- RADIUS client configuration file .SH SYNOPSIS @@ -23,13 +23,19 @@ Output PAM and RADIUS communication debugging information via syslog(3). .TP .I server[:port] secret [timeout] [src_ip] the port name or number is optional. The default ports are not -part of the code base, and are retrieved from +part of the code base, and are retrieved from the services database, e.g. .IR /etc/services . The ports used are -.BR " radius " +.B radius for authentication and -.BR " radacct " +.B radacct for accounting. +If the port is specified as numeric, port+1 is used as the accounting +port. If a name is used for the port that is not +.BR radius , +.B radacct +is still used for accounting. There is no way to specify a port to +be used just for accounting. .P The timeout field is optional. The default timeout is 3 seconds. .IP @@ -52,6 +58,14 @@ setting the src_ip is desired .I vrf-name VRFNAME If the management network is in a VRF, set this variable to the VRF name. This would usually be "mgmt". This is not normally needed with PAM. +.TP +.I priv-lvl VALUE +This sets the minimum privilege level in VSA attribute +.B shell:priv-lvl=VALUE +to be considered a +privileged login (ability to configure via nclu 'net' commands, and able to sudo. +The default is 15. The range is 0-15. Only matters when the VSA attribute is +returned. .SH "SEE ALSO" .BR pam_radius_auth (8), .BR nss_mapuser (5) diff --git a/pam_radius_auth.conf b/pam_radius_auth.conf index 18cd8fd..199d383 100644 --- a/pam_radius_auth.conf +++ b/pam_radius_auth.conf @@ -12,9 +12,14 @@ # # server[:port] secret [timeout] [src_ip] # -# the port name or number is optional. The default port name is -# "radius", and is looked up from /etc/services The timeout field is -# optional. The default timeout is 3 seconds. +# The port name or number is optional. The default port name is +# "radius", and "radacct" for accounting. They are looked up +# in the services database (e.g., /etc/services) +# The timeout field is optional; the default timeout is 3 seconds. +# If the port is specified as numeric, port+1 is used as the accounting +# port. If a name is used for the port that is not "radius", "radacct" +# is still used for accounting. +# There is no way to specify the port to be used for accounting. # # For IPv6 literal addresses, the address has to be surrounded by # square brackets as usual. E.g. [2001:0db8:85a3::4]. @@ -43,5 +48,10 @@ # conflict with a valid hostname # vrf-name mgmt # +# Set the minimum privilege level in VSA attribute shell:priv-lvl=VALUE +# to be considered a # privileged login (ability to configure via +# nclu 'net' commands, and able to sudo. The default is 15, range is 0-15. +# priv-lvl 15 +# # Uncomment to enable debugging, can be used instead of altering pam files # debug diff --git a/src/Makefile b/src/Makefile index 36ad14a..0c171d6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,8 +1,11 @@ -all: pam_radius_auth.o md5.o +all: pam_radius_auth.o md5.o support.o pam_radius_auth.o: pam_radius_auth.c pam_radius_auth.h config.h $(CC) $(CFLAGS) -c $< -o $@ +support.o: support.c pam_radius_auth.h config.h + $(CC) $(CFLAGS) -c $< -o $@ + md5.o: md5.c md5.h $(CC) $(CFLAGS) -c $< -o $@ diff --git a/src/pam_radius_auth.c b/src/pam_radius_auth.c index 249a7b2..5bf93ea 100644 --- a/src/pam_radius_auth.c +++ b/src/pam_radius_auth.c @@ -1,5 +1,4 @@ /* - * $Id: pam_radius_auth.c,v 1.39 2007/03/26 05:35:31 fcusack Exp $ * pam_radius_auth * Authenticate a user via a RADIUS session * @@ -22,6 +21,9 @@ * * Some challenge-response code is copyright (c) CRYPTOCard Inc, 1998. * All rights reserved. + * + * Copyright (C) 2017, 2018 Cumulus Networks, Inc. + * All rights reserved. */ #define PAM_SM_AUTH @@ -30,7 +32,10 @@ #include "pam_radius_auth.h" -#define DPRINT if (debug) _pam_log +#define DPRINT if (debug || cfg_debug) _pam_log + +static int cfg_debug; +static int cleaned_up; /* logging */ static void _pam_log(pam_handle_t * pamh, int err, CONST char *format, ...) @@ -43,25 +48,25 @@ static void _pam_log(pam_handle_t * pamh, int err, CONST char *format, ...) } -/* argument parsing */ +/* base config, plus config file, but no pam cmdline */ +static radius_conf_t savconf = {.min_priv_lvl = 15, /* default priv level */ + .conf_file = CONF_FILE, /* default config file */ + .prompt = DEFAULT_PROMPT, /* default prompt */ +}; + +/* pam cmdline argument parsing */ static int _pam_parse(pam_handle_t * pamh, int argc, CONST char **argv, radius_conf_t * conf) { int ctrl = 0; - memset(conf, 0, sizeof(radius_conf_t)); /* ensure it's initialized */ - - conf->conf_file = CONF_FILE; - - /* set the default prompt */ - snprintf(conf->prompt, MAXPROMPT, "%s: ", DEFAULT_PROMPT); + *conf = savconf; /* initialze from the static config */ /* * If either is not there, then we can't parse anything. */ - if ((argc == 0) || (argv == NULL)) { + if ((argc == 0) || (argv == NULL)) return ctrl; - } /* step through arguments */ for (ctrl = 0; argc-- > 0; ++argv) { @@ -136,7 +141,7 @@ static int _pam_parse(pam_handle_t * pamh, int argc, CONST char **argv, } /* Callback function used to free the saved return value for pam_setcred. */ -void _int_free(pam_handle_t * pamh, void *x, int error_status) +static void _int_free(pam_handle_t * pamh, void *x, int error_status) { free(x); } @@ -145,27 +150,33 @@ void _int_free(pam_handle_t * pamh, void *x, int error_status) * SMALL HELPER FUNCTIONS *************************************************************************/ -/* - * A strerror_r() wrapper function to deal with its nuisances. - */ -static void get_error_string(int errnum, char *buf, size_t buflen) +/* useful for debugging, and maybe some user messages. */ +__attribute__ ((unused)) +static char *ai_ntop(const struct sockaddr *sa) { -#if !defined(__GLIBC__) || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE) - /* XSI version of strerror_r(). */ - int retval = strerror_r(errnum, buf, buflen); - - /* POSIX does not state what will happen to the buffer if the function fails. - * Put it into a known state rather than leave it possibly uninitialized. */ - if (retval != 0 && buflen > (size_t) 0) { - buf[0] = '\0'; - } -#else - /* GNU version of strerror_r(). */ - char tmp_buf[BUFFER_SIZE]; - char *retval = strerror_r(errnum, tmp_buf, sizeof(tmp_buf)); + static char server_address[INET6_ADDRSTRLEN + 16]; - snprintf(buf, buflen, "%s", retval); -#endif + switch (sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), + server_address, INET_ADDRSTRLEN); + + snprintf(server_address + strlen(server_address), 14, ":%hu", + htons(((struct sockaddr_in *)sa)->sin_port)); + break; + + case AF_INET6: + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), + server_address, INET6_ADDRSTRLEN); + + snprintf(server_address + strlen(server_address), 14, ":%hu", + htons(((struct sockaddr_in6 *)sa)->sin6_port)); + break; + + default: + strcpy(server_address, "Unknown AF"); + } + return server_address; } /* @@ -193,15 +204,15 @@ static int get_ipaddr(char *host, struct sockaddr *addr, char *port) /* * take server->hostname, and convert it to server->ip + * Done once when server list parsed. The last part, the + * if port isn't set in config, it needs to be set to either + * radius or raddacct */ -static int host2server(int debug, radius_server_t * server) +static int host2server(pam_handle_t * pamh, radius_server_t * server) { - char hostbuffer[256]; - char tmp[256]; - char *hostname; - char *portstart; - char *p, *port; - int r, n; + char hostbuffer[256], tmpn[11]; + char *hostname, *portstart, *p, *port = NULL, *portacct = NULL; + int retval, retvala; /* hostname might be [ipv6::address] */ strncpy(hostbuffer, server->hostname, sizeof(hostbuffer) - 1); @@ -215,24 +226,70 @@ static int host2server(int debug, radius_server_t * server) portstart = p; } } - if ((port = strchr(portstart, ':')) != NULL) { + if ((port = strchr(portstart, ':')) != NULL) *port++ = '\0'; - if (isdigit((unsigned char)*port) && server->accounting) { + + /* + * Use the configured port if set, otherwise if accounting, + * "radacct", else "radius". If configured port is numeric, + * and we are doing accounting, add 1. + * if they specified a name, and it's "radius", use "radacct" + * for the accounting port. Otherwise, warn them that we'll + * have problems with accounting. + */ + if (port) { + if (isdigit((unsigned char)*port)) { + int n; + /* + * normal accounting port 1 higher than auth port, so + * assume that's true if they gave a numeric + * port, and add 1 to it, when doing accounting + */ if (sscanf(port, "%d", &n) == 1) { - snprintf(tmp, sizeof(tmp), "%d", n + 1); - port = tmp; + snprintf(tmpn, sizeof(tmpn), "%d", n + 1); + portacct = tmpn; } + } else { + portacct = "radacct"; + if (strcmp(port, "radius")) + _pam_log(pamh, LOG_WARNING, "Server %s uses" + " non-standard port '%s', using %s for" + " accounting", server->hostname, port, + portacct); } - } else { - if (server->accounting) - port = "radacct"; - else - port = "radius"; } - + if (!port) + port = "radius"; + if (!portacct) + portacct = "radacct"; + + server->hostpart = strdup(hostname); + if (!server->hostpart) + _pam_log(pamh, LOG_ERR, "Memory allocation error saving" + " hostname %s in server %s info: %m", hostname, + server->hostname); + + /* set up sockaddr for sendto calls */ server->ip = (struct sockaddr *)&server->ip_storage; - r = get_ipaddr(hostname, server->ip, port); - return r; + server->ip_acct = (struct sockaddr *)&server->ipacct_storage; + + retval = get_ipaddr(server->hostpart, server->ip, port); + if (retval) { + _pam_log(pamh, LOG_WARNING, + "Failed looking up IP address for" + " server %s port %s (error=%s)", + server->hostpart, port, gai_strerror(retval)); + server->family = AF_INET; /* assume, for sanity */ + } else + server->family = server->ip->sa_family; + + retvala = get_ipaddr(server->hostpart, server->ip_acct, portacct); + if (retvala) + _pam_log(pamh, LOG_WARNING, + "Failed looking up IP address for" + " accounting server %s port %s (error=%s)", + server->hostpart, portacct, gai_strerror(retval)); + return retval + retvala; } /* @@ -380,6 +437,55 @@ static attribute_t *find_attribute(AUTH_HDR * response, unsigned char type) return attr; } +struct radius_vsa { + unsigned char vendorid[4]; + unsigned char vendor_type; + unsigned char vendor_len; + unsigned char string[1]; +}; + +/* + * Find the VSA attribute with the shell:priv-lvl string if present. + * If present, return the integer value, otherwise return -1. + */ +static int priv_from_vsa(AUTH_HDR * response) +{ + int ret = -1; + int len = ntohs(response->length) - AUTH_HDR_LEN; + attribute_t *attr = (attribute_t *) & response->data; + const char shellpriv[] = "shell:priv-lvl"; + const int slen = strlen(shellpriv); + + while (len > 0) { + if (attr->attribute == PW_VENDOR_SPECIFIC) { + struct radius_vsa *v = (struct radius_vsa *)attr->data; + int j, s; + j = attr->length - 6; + if (j < 0) + j = 0; + s = slen + 1; + if (j > s) { + const char *vsastr = (char *)v->string; + /* skip over the '*' or '=' that should follow + * the attrname */ + if (!strncmp(shellpriv, vsastr, slen)) { + char *e; + int priv; + priv = (int)strtol(&vsastr[s], &e, 0); + if (e > &vsastr[s]) { + ret = priv; + break; + } + + } + } + } + len -= attr->length; + attr = (attribute_t *) ((char *)attr + attr->length); + } + return ret; +} + /* * Add an attribute to a RADIUS packet. */ @@ -424,16 +530,15 @@ static void add_nas_ip_address(AUTH_HDR * request, char *hostname) while (ai != NULL) { if (!v4seen && ai->ai_family == AF_INET) { v4seen = 1; - r = ((struct sockaddr_in *)ai->ai_addr)->sin_addr. - s_addr; + r = ((struct sockaddr_in *) + ai->ai_addr)->sin_addr.s_addr; add_int_attribute(request, PW_NAS_IP_ADDRESS, ntohl(r)); } if (!v6seen && ai->ai_family == AF_INET6) { v6seen = 1; add_attribute(request, PW_NAS_IPV6_ADDRESS, (unsigned char *)&((struct sockaddr_in6 *) - ai->ai_addr)-> - sin6_addr, 16); + ai->ai_addr)->sin6_addr, 16); } ai = ai->ai_next; } @@ -514,262 +619,319 @@ static void add_password(AUTH_HDR * request, unsigned char type, } } -static void cleanup(radius_server_t * server) +/* called from _pam_end() via pam_set_data() arg to cleanup memory and fd's */ +static void cleanup_conf(pam_handle_t * pamh, void *arg, int unused) { - radius_server_t *next; + radius_server_t *next, *server; - while (server) { + for (server = (radius_server_t *) arg; server; server = next) { + if (server->sockfd != -1) { + close(server->sockfd); + server->sockfd = -1; + } next = server->next; - _pam_drop(server->hostname); _pam_forget(server->secret); + _pam_drop(server->port); + _pam_drop(server->hostname); + _pam_drop(server->hostpart); _pam_drop(server); - server = next; } } /* - * allocate and open a local port for communication with the RADIUS - * server - */ -static int initialize(pam_handle_t * pamh, radius_conf_t * conf, int accounting) + * Parse the config file (not the PAM cmdline args) + * Only do once, since all the entry points call this. _pam_parse + * has to be done per entry point, because it can have command line + * args that are different, but the config file only needs to be read + * once (again, except for the very rare case of different config files + * being specified in the PAM lines, which the stat check will catch). + * + * Returns 0 if parsed and OK + * Returns 1 if file already parsed and no change in mtime + * Returns -1 if errors +*/ +static int parse_conffile(pam_handle_t * pamh, radius_conf_t * cf) { - struct sockaddr_storage salocal; - struct sockaddr_storage salocal4; - struct sockaddr_storage salocal6; - char hostname[BUFFER_SIZE]; - char secret[BUFFER_SIZE]; - char vrfname[64]; - - char buffer[BUFFER_SIZE]; + static struct stat last_st; + int line = 0, timeout; + const char *cfname = cf->conf_file; char *p; - FILE *fserver; radius_server_t *server = NULL, *tmp; - int timeout; - int line = 0, scancnt; - char src_ip[MAX_IP_LEN]; - int seen_v6 = 0; + FILE *fserver; + char hostname[BUFFER_SIZE], secret[BUFFER_SIZE], buffer[BUFFER_SIZE]; + char srcip[BUFFER_SIZE]; + + if (!cfname || !*cfname) + return -1; + + if (last_st.st_ino) { + struct stat st; + int rst; + rst = stat(cfname, &st); + if (!rst && st.st_ino == last_st.st_ino && st.st_mtime == + last_st.st_mtime && st.st_ctime == last_st.st_ctime) { + return 1; + } + } - memset(&salocal4, 0, sizeof(salocal4)); - memset(&salocal6, 0, sizeof(salocal6)); - ((struct sockaddr *)&salocal4)->sa_family = AF_INET; - ((struct sockaddr *)&salocal6)->sa_family = AF_INET6; + if (cf->server) /* we already had sockets open and bound, cleanup */ + pam_set_data(pamh, "rad_conf_cleanup", NULL, NULL); /* the first time around, read the configuration file */ - if ((fserver = fopen(conf->conf_file, "r")) == (FILE *) NULL) { - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, sizeof(error_string)); + if ((fserver = fopen(cfname, "r")) == (FILE *) NULL) { _pam_log(pamh, LOG_ERR, "Could not open configuration file %s:" - " %s\n", conf->conf_file, error_string); - return PAM_ABORT; + " %m", cfname); + return -1; } - vrfname[0] = '\0'; - while (!feof(fserver) - && (fgets(buffer, sizeof(buffer), fserver) != (char *)NULL) - && (!ferror(fserver))) { + while (!feof(fserver) && + (fgets(buffer, sizeof(buffer), fserver) != (char *)NULL) && + (!ferror(fserver))) { + int scancnt; + line++; p = buffer; - /* - * Skip whitespace - */ + /* Skip leading whitespace */ while ((*p == ' ') || (*p == '\t')) p++; - /* - * Skip blank lines and comments. - */ + /* Skip blank lines and comments. */ if ((*p == '\r') || (*p == '\n') || (*p == '#')) continue; - /* - * Error out if the text is too long. - */ + /* Error out if the text is too long. */ if (!*p) { _pam_log(pamh, LOG_ERR, "ERROR reading %s, line %d:" - " Line too long\n", conf->conf_file, line); + " Line too long", cfname, line); break; } + srcip[0] = '\0'; scancnt = - sscanf(p, "%s %s %d %s", hostname, secret, &timeout, - src_ip); + sscanf(p, "%s %s %d %s", hostname, secret, &timeout, srcip); - /* is it the name of a vrf we should bind to? */ if (!strcmp(hostname, "vrf-name")) { + /* is it the name of a vrf we should bind to? */ if (scancnt < 2) _pam_log(pamh, LOG_ERR, "ERROR reading %s, line %d:" - " only %d fields\n", conf->conf_file, - line, scancnt); + " only %d fields", cf->conf_file, line, + scancnt); else - snprintf(vrfname, sizeof vrfname, "%s", secret); + snprintf(cf->vrfname, sizeof cf->vrfname, "%s", + secret); + snprintf(savconf.vrfname, sizeof savconf.vrfname, + "%s", secret); continue; - } - - /* allow setting debug in config file as well */ - if (!strcmp(hostname, "debug")) { - if (scancnt < 1) - _pam_log(pamh, LOG_ERR, - "ERROR reading %s, line %d:" - " only %d fields\n", conf->conf_file, - line, scancnt); - else - conf->debug = 1; + } else if (!strcmp(hostname, "priv-lvl")) { + /* privilege level for privileged logins */ + if (scancnt < 2) + _pam_log(pamh, LOG_ERR, "ERROR reading %s, line" + " %d: only %d fields", cfname, line, + scancnt); + else { + unsigned long val; + char *ok; + val = strtoul(secret, &ok, 0); + if (ok == secret || val > 15UL || + (*ok && !isspace(*ok))) + _pam_log(pamh, LOG_ERR, "Invalid number" + " (%s) \"%s\" in %s line %d:", + secret, hostname, cfname, + line); + else { + cf->min_priv_lvl = (unsigned)val; + savconf.min_priv_lvl = cf->min_priv_lvl; + } + } + continue; + } else if (!strcmp(hostname, "debug")) { + /* allow setting debug in config file as well */ + cf->debug = cfg_debug = 1; continue; } if (scancnt < 2) { _pam_log(pamh, LOG_ERR, "ERROR reading %s, line %d:" - " only %d fields\n", conf->conf_file, line, - scancnt); + " only %d fields", cfname, line, scancnt); continue; /* invalid line */ } - if (scancnt < 4) { - src_ip[0] = 0; - if (scancnt < 3) - timeout = 3; /* default timeout */ - } + if (scancnt < 3) + timeout = 3; /* default timeout */ /* read it in and save the data */ - tmp = malloc(sizeof(radius_server_t)); + tmp = calloc(sizeof(radius_server_t), 1); + if (!tmp) { + _pam_log(pamh, LOG_ERR, + "Unable to allocate server info for %s: %m", + hostname); + return -1; + } + tmp->sockfd = -1; /* mark as uninitialized */ if (server) { server->next = tmp; server = server->next; } else { - conf->server = tmp; + cf->server = tmp; server = tmp; /* first time */ } - /* sometime later do memory checks here */ + if (srcip[0]) + snprintf(server->src_ip, sizeof server->src_ip, "%s", + srcip); + server->hostname = strdup(hostname); server->secret = strdup(secret); - server->accounting = accounting; + if (!server->hostname || !server->secret) + _pam_log(pamh, LOG_ERR, "Memory allocation error saving" + " server %s info: %m", hostname); - memset(&server->ip, 0, sizeof server->ip); - if ((timeout < 1) || (timeout > 60)) { + if ((timeout < 1) || (timeout > 60)) server->timeout = 3; - } else { + else server->timeout = timeout; - } server->next = NULL; - if (src_ip[0]) { - memset(&salocal, 0, sizeof(salocal)); - get_ipaddr(src_ip, (struct sockaddr *)&salocal, NULL); - switch (salocal.ss_family) { - case AF_INET: - memcpy(&salocal4, &salocal, sizeof(salocal)); - break; - case AF_INET6: - seen_v6 = 1; - memcpy(&salocal6, &salocal, sizeof(salocal)); - break; - } - } } fclose(fserver); - if (!server) { /* no server found, die a horrible death */ - _pam_log(pamh, LOG_ERR, "No RADIUS server found in" - " configuration file %s\n", conf->conf_file); - return PAM_AUTHINFO_UNAVAIL; + /* save config file info for test on multiple calls */ + if (stat(cfname, &last_st) == -1) + _pam_log(pamh, LOG_ERR, "Error stat'ing config file %s: %m", + cfname); + + if (!cf->server) { /* no server found, die a horrible death */ + _pam_log(pamh, LOG_ERR, "No server found in" + " configuration file %s", cf->conf_file); + return -1; } /* - * FIXME- we could have different source-ips for different servers, so - * sockfd should probably be in the server struct, not in the conf struct. + * save the server in savconf for next call (if any) to _parse_args() + * for the same config file (will be overridden if a different config + * file */ + savconf.server = cf->server; + + return 0; +} - /* open a socket. Dies if it fails */ - conf->sockfd = socket(AF_INET, SOCK_DGRAM, 0); - if (conf->sockfd < 0) { - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, sizeof(error_string)); - _pam_log(pamh, LOG_ERR, "Failed to open RADIUS socket: %s\n", - error_string); - return PAM_AUTHINFO_UNAVAIL; +static int setup_sock(pam_handle_t * pamh, radius_server_t * server, + radius_conf_t * conf) +{ + struct sockaddr_storage nullip; + struct sockaddr *addr; + char *hname; + size_t sockaddrsz; + int debug = conf->debug; + + if (host2server(pamh, server)) + return 1; + + memset(&nullip, 0, sizeof nullip); + addr = (struct sockaddr *)&nullip; + if (server->src_ip[0]) { + int r; + /* bind to specified source IP and family */ + hname = server->src_ip; + r = get_ipaddr(hname, addr, NULL); + if (r) + _pam_log(pamh, LOG_WARNING, + "Failed looking up source IP address %s for" + " server %s (error=%s)", + server->src_ip, server->hostname, + gai_strerror(r)); + } else + hname = server->hostpart; + + addr->sa_family = server->family; + + server->sockfd = socket(server->family, SOCK_DGRAM, 0); + if (server->sockfd == -1) { + _pam_log(pamh, LOG_WARNING, "Failed to open socket for" + " %s: %m", server->hostname); + return 1; } + + /* warn only, not fatal */ + if (fcntl(server->sockfd, F_SETFD, FD_CLOEXEC)) + _pam_log(pamh, LOG_WARNING, "Set sockets close on exec failed" + " for %s: %m", server->hostname); + #ifndef HAVE_POLL_H - if (conf->sockfd >= FD_SETSIZE) { + if (server->sockfd >= FD_SETSIZE) { _pam_log(pamh, LOG_ERR, "Unusable socket, FD is larger than" - " %d\n", FD_SETSIZE); - close(conf->sockfd); - return PAM_AUTHINFO_UNAVAIL; + " %d", FD_SETSIZE); + close(server->sockfd); + server->sockfd = -1; + return 1; } #endif + sockaddrsz = server->family == AF_INET ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6); + + if (bind(server->sockfd, addr, sockaddrsz) < 0) { + _pam_log(pamh, LOG_ERR, "Bind for server %s failed: %m", hname); + /* mark sockfd as not usable, by closing and set to -1 */ + close(server->sockfd); + server->sockfd = -1; + return 1; + } - if (vrfname[0]) { + if (conf->vrfname[0]) { /* do not fail if the bind fails, connection may succeed */ - if (setsockopt(conf->sockfd, SOL_SOCKET, SO_BINDTODEVICE, - vrfname, strlen(vrfname) + 1) < 0) + if (setsockopt(server->sockfd, SOL_SOCKET, SO_BINDTODEVICE, + conf->vrfname, + strlen(conf->vrfname) + 1) == -1) { _pam_log(pamh, LOG_WARNING, - "Binding IPv4 socket to VRF %s" " failed: %m", - vrfname); - else if (conf->debug) - _pam_log(pamh, LOG_DEBUG, "Configured IPv4 vrf as: %s", - vrfname); + "Binding host %s socket to VRF %s" + " failed: %m", server->hostname, + conf->vrfname); + } + DPRINT(pamh, LOG_DEBUG, "Configured server %s vrf as: %s", + server->hostname, conf->vrfname); } + return 0; +} - /* set up the local end of the socket communications */ - if (bind - (conf->sockfd, (struct sockaddr *)&salocal4, - sizeof(struct sockaddr_in)) < 0) { - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, sizeof(error_string)); - _pam_log(pamh, LOG_ERR, "Failed binding to port: %s", - error_string); - close(conf->sockfd); - return PAM_AUTHINFO_UNAVAIL; +/* + * allocate and open a local port for communication with the RADIUS + * server. + * The server connection only needs to be redone if there are arguments, + * and they are different (rare). + */ +static int initialize(pam_handle_t * pamh, radius_conf_t * conf) +{ + int ret = PAM_SUCCESS, retsetup, nservers = 0; + radius_server_t *server = NULL; + + ret = parse_conffile(pamh, conf); + if (ret == -1) + return ret; + else if (ret == 1) + return PAM_SUCCESS; /* no changes to previous parse */ + + /* setup the sockets, bind to them, etc. */ + for (server = conf->server; server; server = server->next) { + retsetup = setup_sock(pamh, server, conf); + if (retsetup == PAM_SUCCESS) + nservers++; } - /* open a IPv6 socket. Dies if it fails */ - conf->sockfd6 = socket(AF_INET6, SOCK_DGRAM, 0); - if (conf->sockfd6 < 0) { - if (!seen_v6) - return PAM_SUCCESS; - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, sizeof(error_string)); - _pam_log(pamh, LOG_ERR, "Failed to open RADIUS IPv6 socket:" - " %s\n", error_string); - close(conf->sockfd); - return PAM_AUTHINFO_UNAVAIL; - } -#ifndef HAVE_POLL_H - if (conf->sockfd6 >= FD_SETSIZE) { - _pam_log(pamh, LOG_ERR, "Unusable socket, FD is larger than" - " %d\n", FD_SETSIZE); - close(conf->sockfd); - close(conf->sockfd6); - return PAM_AUTHINFO_UNAVAIL; - } -#endif - if (vrfname[0]) { - /* do not fail if the bind fails, connection may succeed */ - if (setsockopt(conf->sockfd6, SOL_SOCKET, SO_BINDTODEVICE, - vrfname, strlen(vrfname) + 1) < 0) - _pam_log(pamh, LOG_WARNING, - "Binding IPv6 socket to VRF %s" " failed: %m", - vrfname); - else if (conf->debug) - _pam_log(pamh, LOG_DEBUG, "Configured IPv6 vrf as: %s", - vrfname); + if (!nservers) { + _pam_log(pamh, LOG_ERR, "No valid server found in configuration" + " file %s", conf->conf_file); + ret = PAM_AUTHINFO_UNAVAIL; } - /* set up the local end of the socket communications */ - if (bind - (conf->sockfd6, (struct sockaddr *)&salocal6, - sizeof(struct sockaddr_in6)) < 0) { - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, sizeof(error_string)); - _pam_log(pamh, LOG_ERR, "Failed binding to IPv6 port: %s", - error_string); - close(conf->sockfd); - close(conf->sockfd6); - return PAM_AUTHINFO_UNAVAIL; + if (conf->server) { + cleaned_up = 0; + pam_set_data(pamh, "rad_conf_cleanup", (void *)conf->server, + cleanup_conf); } - return PAM_SUCCESS; + return ret; } /* @@ -832,7 +994,7 @@ static void build_radius_packet(AUTH_HDR * request, CONST char *user, */ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, AUTH_HDR * response, char *password, char *old_password, - int tries, pam_handle_t * pamh) + int tries, pam_handle_t * pamh, int accounting) { int total_length; #ifdef HAVE_POLL_H @@ -860,44 +1022,34 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, On the other hand, we could just try all of the servers again in sequence, on the off chance that one may have ended up fixing itself. - */ /* loop over all available servers */ while (server != NULL) { - /* clear the response */ - memset(response, 0, sizeof(AUTH_HDR)); + sockfd = server->sockfd; + struct sockaddr *addr = + accounting ? server->ip_acct : server->ip; - /* only look up IP information as necessary */ - retval = host2server(conf->debug, server); - if (retval != 0) { - _pam_log(pamh, LOG_ERR, - "Failed looking up IP address for RADIUS server %s (error=%s)", - server->hostname, gai_strerror(retval)); + if (sockfd == -1) { ok = FALSE; - goto next; /* skip to the next server */ + goto next; /* try next server, if any */ } + /* clear the response */ + memset(response, 0, sizeof(AUTH_HDR)); + if (!password) { /* make an RFC 2139 p6 request authenticator */ get_accounting_vector(request, server); } - sockfd = - server->ip->sa_family == - AF_INET ? conf->sockfd : conf->sockfd6; total_length = ntohs(request->length); server_tries = tries; send: /* send the packet */ if (sendto(sockfd, (char *)request, total_length, 0, - server->ip, sizeof(struct sockaddr_storage)) < 0) { - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, - sizeof(error_string)); - _pam_log(pamh, LOG_ERR, - "Error sending RADIUS packet to" - " server %s: %s", server->hostname, - error_string); + addr, sizeof(struct sockaddr_storage)) < 0) { + _pam_log(pamh, LOG_ERR, "Error sending packet to" + " server %s: %m", server->hostname); ok = FALSE; goto next; /* skip to the next server */ } @@ -931,12 +1083,15 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, /* timed out */ if (rcode == 0) { - _pam_log(pamh, LOG_ERR, "RADIUS server %s" - " failed to respond", - server->hostname); if (--server_tries) { + _pam_log(pamh, LOG_WARNING, "server %s" + " no reponse, retrying", + server->hostname); goto send; } + _pam_log(pamh, LOG_ERR, "server %s" + " failed to respond", + server->hostname); ok = FALSE; break; /* exit from the loop */ } else if (rcode < 0) { @@ -947,7 +1102,7 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, if (now > end) { _pam_log(pamh, LOG_ERR, - "RADIUS server %s " + "server %s " "failed to respond", server->hostname); if (--server_tries) @@ -960,40 +1115,31 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, if (tv.tv_sec == 0) { /* keep waiting */ tv.tv_sec = 1; } - } else { /* not an interrupt, it was a real error */ - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, - sizeof(error_string)); - _pam_log(pamh, LOG_ERR, - "Error waiting" - " for response from RADIUS" - " server %s: %s", - server->hostname, - error_string); + } else { /* a real error */ + _pam_log(pamh, LOG_ERR, "Error waiting" + " for response from" + " server %s: %m", + server->hostname); ok = FALSE; break; } - - /* the call returned OK */ + } else /* the poll/select returned OK */ #ifdef HAVE_POLL_H - } else if (pollfds[0].revents & POLLIN) { + if (pollfds[0].revents & POLLIN) #else - } else if (FD_ISSET(sockfd, &set)) { + if (FD_ISSET(sockfd, &set)) #endif + { /* try to receive some data */ if ((total_length = recvfrom(sockfd, (void *)response, BUFFER_SIZE, 0, NULL, NULL)) < 0) { - char error_string[BUFFER_SIZE]; - get_error_string(errno, error_string, - sizeof(error_string)); _pam_log(pamh, LOG_ERR, "error reading" - " RADIUS packet from server" - " %s: %s", server->hostname, - error_string); + "response from server" + " %s: %m", server->hostname); ok = FALSE; break; @@ -1006,7 +1152,7 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, || (ntohs(response->length) > BUFFER_SIZE)) { _pam_log(pamh, LOG_ERR, - "RADIUS packet from " + "response from " "server %s is " "corrupted", server->hostname); @@ -1014,18 +1160,23 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, break; } - /* Check if we have the data OK. We should also check request->id */ + /* Check if we have the data OK. + * We should also check request->id */ if (password) { if (old_password) { #ifdef LIVINGSTON_PASSWORD_VERIFY_BUG_FIXED - p = old_password; /* what it should be */ + /* what it should be */ + p = old_password; #else - p = ""; /* what it really is */ + /* what it really is */ + p = ""; #endif } /* - * RFC 2139 p.6 says not do do this, but the Livingston 1.16 - * server disagrees. If the user says he wants the bug, give in. + * RFC 2139 p.6 says not do do this, but + * the Livingston 1.16 server disagrees. + * If the user says he wants the bug, + * give in. */ } else { /* authentication request */ if (conf->accounting_bug) { @@ -1036,9 +1187,9 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, if (!verify_packet (p, response, request)) { _pam_log(pamh, LOG_ERR, - "packet" - " from RADIUS server %s" - " failed verification:" + "response from server" + " %s failed" + " verification:" " The shared secret is" " probably incorrect.", server->hostname); @@ -1047,7 +1198,8 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, } /* - * Check that the response ID matches the request ID. + * Check that the response ID matches + * the request ID. */ if (response->id != request->id) { _pam_log(pamh, LOG_WARNING, @@ -1064,61 +1216,45 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, } /* - * Whew! The poll is done. It hasn't timed out, or errored out. - * It's our descriptor. We've got some data. It's the right size. - * The packet is valid. - * NOW, we can skip out of the loop, and process the packet + * Whew! The poll is done. It hasn't timed out, + * or errored out. It's our descriptor. + * We've got some data. It's the right size. + * The packet is valid. NOW, we can skip out of + * the loop, and process the packet */ break; } - /* otherwise, we've got data on another descriptor, keep checking the network */ + /* otherwise, we've got data on another descriptor, keep + * checking the network */ } - - /* go to the next server if this one didn't respond */ - next: - if (!ok) { - radius_server_t *old; /* forget about this server */ - - old = server; - server = server->next; - conf->server = server; - - _pam_forget(old->secret); - free(old->hostname); - free(old); - - if (server) { /* if there's more servers to check */ - /* get a new authentication vector, and update the passwords */ + next: /* go to the next server if this one didn't respond */ + if (ok) + break; + server = server->next; + if (server) { /* if there's more servers to check */ + /* get a new authentication vector, and update + * the passwords */ + get_random_vector(request->vector); + request->id = request->vector[0]; + + /* update passwords, as appropriate */ + if (password) { get_random_vector(request->vector); - request->id = request->vector[0]; - - /* update passwords, as appropriate */ - if (password) { - get_random_vector(request->vector); - if (old_password) { /* password change request */ - add_password(request, - PW_PASSWORD, - password, - old_password); - add_password(request, - PW_OLD_PASSWORD, - old_password, - old_password); - } else { /* authentication request */ - add_password(request, - PW_PASSWORD, - password, - server->secret); - } + if (old_password) { + /* password change request */ + add_password(request, + PW_PASSWORD, + password, old_password); + add_password(request, + PW_OLD_PASSWORD, + old_password, + old_password); + } else { /* authentication request */ + add_password(request, + PW_PASSWORD, + password, server->secret); } } - continue; - - } else { - /* we've found one that does respond, forget about the other servers */ - cleanup(server->next); - server->next = NULL; - break; } } @@ -1128,9 +1264,8 @@ static int talk_radius(radius_conf_t * conf, AUTH_HDR * request, retval = PAM_IGNORE; else retval = PAM_AUTHINFO_UNAVAIL; - } else { + } else retval = PAM_SUCCESS; - } return retval; } @@ -1149,19 +1284,19 @@ static int rad_converse(pam_handle_t * pamh, int msg_style, char *message, { CONST struct pam_conv *conv; struct pam_message resp_msg; - CONST struct pam_message *msg[1]; + CONST struct pam_message *msg; struct pam_response *resp = NULL; int retval; resp_msg.msg_style = msg_style; resp_msg.msg = message; - msg[0] = &resp_msg; + msg = &resp_msg; /* grab the password */ retval = pam_get_item(pamh, PAM_CONV, (CONST void **)&conv); PAM_FAIL_CHECK; - retval = conv->conv(1, msg, &resp, conv->appdata_ptr); + retval = conv->conv(1, &msg, &resp, conv->appdata_ptr); PAM_FAIL_CHECK; if (password) { /* assume msg.type needs a response */ @@ -1180,16 +1315,63 @@ static int rad_converse(pam_handle_t * pamh, int msg_style, char *message, return PAM_SUCCESS; } +/* + * We'll create the home directory if needed, and we'll write the flat file + * mapping entry. It's done at this point, because this is the end of the + * authentication phase (and authorization, too, since authorization is part of + * authentication phase for RADIUS) for ssh, login, etc. + */ +static void +setup_userinfo(pam_handle_t * pamh, const char *user, int debug, int privileged) +{ + struct passwd *pw; + + /* + * set SUDO_PROMPT in env so that it prompts as the login user, not the mapped + * user, unless (unlikely) the prompt has already been set. + * It won't hurt to do this if the user wasn't mapped. + */ + if (!pam_getenv(pamh, "SUDO_PROMPT")) { + char nprompt[strlen("SUDO_PROMPT=[sudo] password for ") + strlen(user) + 3]; /* + 3 for ": " and the \0 */ + snprintf(nprompt, sizeof nprompt, + "SUDO_PROMPT=[sudo] password for %s: ", user); + if (pam_putenv(pamh, nprompt) != PAM_SUCCESS) + _pam_log(pamh, LOG_NOTICE, + "failed to set PAM sudo prompt " "(%s)", + nprompt); + } + pw = getpwnam(user); /* this should never fail, at this point... */ + if (!pw) { + if (debug) + pam_syslog(pamh, LOG_DEBUG, + "Failed to get homedir for user (%s)", user); + return; + } + + /* + * We don't "fail" on errors here, since they are not fatal for + * the session, although they can result in name or uid lookups not + * working correctly. + */ + __write_mapfile(pamh, user, pw->pw_uid, privileged, debug); + __chk_homedir(pamh, user, pw->pw_dir, debug); +} + +/* this is used so that sm_auth returns an appropriate value */ +static void inline setcred_return(pam_handle_t * pamh, int val) +{ + int *pret = malloc(sizeof(int)); + *pret = val; + pam_set_data(pamh, "rad_setcred_return", (void *)pret, _int_free); +} + /************************************************************************** * GENERAL CODE **************************************************************************/ #undef PAM_FAIL_CHECK #define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { \ - int *pret = malloc(sizeof(int)); \ - *pret = retval; \ - pam_set_data(pamh, "rad_setcred_return", (void *) pret, _int_free); \ - return retval; } + goto do_next; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, CONST char **argv) @@ -1199,8 +1381,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, char *password = NULL; CONST char *rhost; char *resp2challenge = NULL; - int ctrl; - int debug; + int ctrl, debug = 0; int retval = PAM_AUTH_ERR; int num_challenge = 0; @@ -1211,23 +1392,27 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, radius_conf_t config; ctrl = _pam_parse(pamh, argc, argv, &config); - debug = config.debug; + + /* + * Get the IP address of the authentication server + * Then, open a socket, and bind it to a port + * Called early, so we can set debug flag + */ + retval = initialize(pamh, &config); + PAM_FAIL_CHECK; + + debug = config.debug + cfg_debug; /* grab the user name */ retval = pam_get_user(pamh, &user, NULL); PAM_FAIL_CHECK; /* check that they've entered something, and not too long, either */ - if ((user == NULL) || (strlen(user) > MAXPWNAM)) { - int *pret = malloc(sizeof(int)); - *pret = PAM_USER_UNKNOWN; - pam_set_data(pamh, "rad_setcred_return", (void *)pret, - _int_free); - + if (user == NULL || (strlen(user) > MAXPWNAM)) { + retval = PAM_USER_UNKNOWN; DPRINT(pamh, LOG_DEBUG, "User name was NULL, or too long"); - return PAM_USER_UNKNOWN; } - DPRINT(pamh, LOG_DEBUG, "Got user name %s", user); + PAM_FAIL_CHECK; if (ctrl & PAM_RUSER_ARG) { retval = @@ -1246,13 +1431,6 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, } /* - * Get the IP address of the authentication server - * Then, open a socket, and bind it to a port - */ - retval = initialize(pamh, &config, FALSE); - PAM_FAIL_CHECK; - - /* * If there's no client id specified, use the service type, to help * keep track of which service is doing the authentication. */ @@ -1263,10 +1441,6 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, PAM_FAIL_CHECK; } - /* now we've got a socket open, so we've got to clean it up on error */ -#undef PAM_FAIL_CHECK -#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto do_next; } - /* build and initialize the RADIUS packet */ request->code = PW_AUTHENTICATION_REQUEST; get_random_vector(request->vector); @@ -1294,9 +1468,8 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, /* check to see if we send a NULL password the first time around */ if (!(ctrl & PAM_SKIP_PASSWD)) { - retval = - rad_converse(pamh, PAM_PROMPT_ECHO_OFF, - config.prompt, &password); + retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, + config.prompt, &password); PAM_FAIL_CHECK; } else { @@ -1321,14 +1494,13 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, (unsigned char *)rhost, strlen(rhost)); } - DPRINT(pamh, LOG_DEBUG, "Sending RADIUS request code %d", - request->code); + DPRINT(pamh, LOG_DEBUG, "Sending request code %d", request->code); retval = talk_radius(&config, request, response, password, NULL, - config.retries + 1, pamh); + config.retries + 1, pamh, 0); PAM_FAIL_CHECK; - DPRINT(pamh, LOG_DEBUG, "Got RADIUS response code %d", response->code); + DPRINT(pamh, LOG_DEBUG, "Got response code %d", response->code); /* * If we get an authentication failure, and we sent a NULL password, @@ -1346,7 +1518,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, ((a_reply = find_attribute(response, PW_REPLY_MESSAGE)) == NULL)) { /* Actually, State isn't required. */ - _pam_log(pamh, LOG_ERR, "RADIUS Access-Challenge" + _pam_log(pamh, LOG_ERR, "Access-Challenge" " received with State or Reply-Message" " missing"); retval = PAM_AUTHINFO_UNAVAIL; @@ -1357,7 +1529,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, * Security fixes. */ if ((a_state->length <= 2) || (a_reply->length <= 2)) { - _pam_log(pamh, LOG_ERR, "RADIUS Access-Challenge" + _pam_log(pamh, LOG_ERR, "Access-Challenge" " received with invalid State or" " Reply-Message"); retval = PAM_AUTHINFO_UNAVAIL; @@ -1368,9 +1540,8 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, challenge[a_reply->length - 2] = 0; /* It's full challenge-response, we should have echo on */ - retval = - rad_converse(pamh, PAM_PROMPT_ECHO_ON, challenge, - &resp2challenge); + retval = rad_converse(pamh, PAM_PROMPT_ECHO_ON, challenge, + &resp2challenge); PAM_FAIL_CHECK; /* now that we've got a response, build a new radius packet */ @@ -1387,9 +1558,8 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, add_attribute(request, PW_STATE, a_state->data, a_state->length - 2); - retval = - talk_radius(&config, request, response, resp2challenge, - NULL, 1, pamh); + retval = talk_radius(&config, request, response, + resp2challenge, NULL, 1, pamh, 0); PAM_FAIL_CHECK; DPRINT(pamh, LOG_DEBUG, "Got response to challenge code %d", @@ -1413,6 +1583,26 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, /* Whew! Done the pasword checks, look for an authentication acknowledge */ if (response->code == PW_AUTHENTICATION_ACK) { + int privlvl; + + /* + * get the privilege level via VSA, if present, and save it for the + * accounting entry point + */ + privlvl = priv_from_vsa(response); + if (debug) { + if (privlvl < 0) + _pam_log(pamh, LOG_NOTICE, + "server did not return VSA" + "with shell:priv-lvl"); + else + _pam_log(pamh, LOG_NOTICE, + "server VSA shell:priv-lvl" + "=%d, min for priv=%d", privlvl, + config.min_priv_lvl); + } + setup_userinfo(pamh, user, debug, + privlvl >= config.min_priv_lvl); retval = PAM_SUCCESS; } else { retval = PAM_AUTH_ERR; /* authentication failure */ @@ -1427,18 +1617,9 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, DPRINT(pamh, LOG_DEBUG, "authentication %s", retval == PAM_SUCCESS ? "succeeded" : "failed"); - close(config.sockfd); - if (config.sockfd6 >= 0) - close(config.sockfd6); - cleanup(config.server); _pam_forget(password); _pam_forget(resp2challenge); - { - int *pret = malloc(sizeof(int)); - *pret = retval; - pam_set_data(pamh, "rad_setcred_return", (void *)pret, - _int_free); - } + setcred_return(pamh, retval); return retval; } @@ -1451,23 +1632,23 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags, int argc, CONST char **argv) { - int retval, *pret; + int ret, retval, *pret = NULL; retval = PAM_SUCCESS; - pret = &retval; - pam_get_data(pamh, "rad_setcred_return", (CONST void **)&pret); - return *pret; + ret = pam_get_data(pamh, "rad_setcred_return", (CONST void **)&pret); + return ret == PAM_SUCCESS && pret ? *pret : retval; } #undef PAM_FAIL_CHECK -#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { return PAM_SESSION_ERR; } +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } +/* handle the accounting */ static int pam_private_session(pam_handle_t * pamh, int flags, int argc, CONST char **argv, int status) { CONST char *user; CONST char *rhost; - int retval = PAM_AUTH_ERR; + int retval = PAM_AUTH_ERR, debug; char recv_buffer[4096]; char send_buffer[4096]; @@ -1477,20 +1658,28 @@ static int pam_private_session(pam_handle_t * pamh, int flags, int argc, (void)_pam_parse(pamh, argc, argv, &config); + /* + * Get the IP address of the authentication server + * Then, open a socket, and bind it to a port + * Called early, so we can set debug flag + */ + retval = initialize(pamh, &config); + PAM_FAIL_CHECK; + + debug = config.debug + cfg_debug; + /* grab the user name */ retval = pam_get_user(pamh, &user, NULL); PAM_FAIL_CHECK; /* check that they've entered something, and not too long, either */ if ((user == NULL) || (strlen(user) > MAXPWNAM)) { - return PAM_USER_UNKNOWN; + retval = PAM_USER_UNKNOWN; + PAM_FAIL_CHECK; } - /* - * Get the IP address of the authentication server - * Then, open a socket, and bind it to a port - */ - retval = initialize(pamh, &config, TRUE); + if (status == PW_STATUS_STOP && !__remove_mapfile(pamh, user, debug)) + retval = PAM_USER_UNKNOWN; PAM_FAIL_CHECK; /* @@ -1498,16 +1687,11 @@ static int pam_private_session(pam_handle_t * pamh, int flags, int argc, * keep track of which service is doing the authentication. */ if (!config.client_id) { - retval = - pam_get_item(pamh, PAM_SERVICE, - (CONST void **)&config.client_id); + retval = pam_get_item(pamh, PAM_SERVICE, + (CONST void **)&config.client_id); PAM_FAIL_CHECK; } - /* now we've got a socket open, so we've got to clean it up on error */ -#undef PAM_FAIL_CHECK -#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } - /* build and initialize the RADIUS packet */ request->code = PW_ACCOUNTING_REQUEST; get_random_vector(request->vector); @@ -1552,7 +1736,8 @@ static int pam_private_session(pam_handle_t * pamh, int flags, int argc, (unsigned char *)rhost, strlen(rhost)); } - retval = talk_radius(&config, request, response, NULL, NULL, 1, pamh); + retval = + talk_radius(&config, request, response, NULL, NULL, 1, pamh, 1); PAM_FAIL_CHECK; /* oops! They don't have the right password. Complain and die. */ @@ -1564,12 +1749,6 @@ static int pam_private_session(pam_handle_t * pamh, int flags, int argc, retval = PAM_SUCCESS; error: - - close(config.sockfd); - if (config.sockfd6 >= 0) - close(config.sockfd6); - cleanup(config.server); - return retval; } @@ -1586,7 +1765,7 @@ PAM_EXTERN int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc, } #undef PAM_FAIL_CHECK -#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {return retval; } +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { goto error; } #define MAX_PASSWD_TRIES 3 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, @@ -1599,7 +1778,6 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, int ctrl; int retval = PAM_AUTHTOK_ERR; int attempts; - char recv_buffer[4096]; char send_buffer[4096]; AUTH_HDR *request = (AUTH_HDR *) send_buffer; @@ -1608,6 +1786,13 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, ctrl = _pam_parse(pamh, argc, argv, &config); + /* + * Get the IP address of the authentication server + * Then, open a socket, and bind it to a port + */ + retval = initialize(pamh, &config); + PAM_FAIL_CHECK; + /* grab the user name */ retval = pam_get_user(pamh, &user, NULL); PAM_FAIL_CHECK; @@ -1618,13 +1803,6 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, } /* - * Get the IP address of the authentication server - * Then, open a socket, and bind it to a port - */ - retval = initialize(pamh, &config, FALSE); - PAM_FAIL_CHECK; - - /* * If there's no client id specified, use the service type, to help * keep track of which service is doing the authentication. */ @@ -1635,10 +1813,6 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, PAM_FAIL_CHECK; } - /* now we've got a socket open, so we've got to clean it up on error */ -#undef PAM_FAIL_CHECK -#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } - /* grab the old password (if any) from the previous password layer */ retval = pam_get_item(pamh, PAM_OLDAUTHTOK, (CONST void **)&password); PAM_FAIL_CHECK; @@ -1654,9 +1828,8 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, /* preliminary password change checks. */ if (flags & PAM_PRELIM_CHECK) { if (!password) { /* no previous password: ask for one */ - retval = - rad_converse(pamh, PAM_PROMPT_ECHO_OFF, - config.prompt, &password); + retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, + config.prompt, &password); PAM_FAIL_CHECK; } @@ -1679,7 +1852,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, retval = talk_radius(&config, request, response, password, NULL, 1, - pamh); + pamh, 0); PAM_FAIL_CHECK; /* oops! They don't have the right password. Complain and die. */ @@ -1784,18 +1957,20 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, /* build and initialize the password change request RADIUS packet */ request->code = PW_PASSWORD_REQUEST; get_random_vector(request->vector); - request->id = request->vector[0]; /* this should be evenly distributed */ + /* this should be evenly distributed */ + request->id = request->vector[0]; - /* the secret here can not be know to the user, so it's the new password */ + /* the secret here can not be known to the user, + * so it's the new password */ _pam_forget(config.server->secret); - config.server->secret = strdup(password); /* it's free'd later */ + /* freed in cleanup_conf() */ + config.server->secret = strdup(password); build_radius_packet(request, user, new_password, &config); add_password(request, PW_OLD_PASSWORD, password, password); - retval = - talk_radius(&config, request, response, new_password, - password, 1, pamh); + retval = talk_radius(&config, request, response, new_password, + password, 1, pamh, 0); PAM_FAIL_CHECK; /* Whew! Done password changing, check for password acknowledge */ @@ -1827,11 +2002,6 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, retval == PAM_SUCCESS ? "succeeded" : "failed"); } - close(config.sockfd); - if (config.sockfd6 >= 0) - close(config.sockfd6); - cleanup(config.server); - _pam_forget(password); _pam_forget(new_password); return retval; @@ -1844,38 +2014,8 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, CONST char **argv) { - int retval = PAM_SUCCESS; - CONST char *user; - radius_conf_t config; - - (void)_pam_parse(pamh, argc, argv, &config); - - /* grab the user name */ - retval = pam_get_user(pamh, &user, NULL); - if (retval != PAM_SUCCESS || user == NULL || strlen(user) > MAXPWNAM) { - return PAM_USER_UNKNOWN; - } - - /* - * parse the config file. We don't make any connections here, so ignore - * any failures. For consistency only. - */ - retval = initialize(pamh, &config, FALSE); - - /* - * set SUDO_PROMPT in env so that it prompts as the login user, not the mapped - * user, unless (unlikely) the prompt has already been set. - * It won't hurt to do this if the user wasn't mapped. - */ - if (!pam_getenv(pamh, "SUDO_PROMPT")) { - char nprompt[strlen("SUDO_PROMPT=[sudo] password for ") + strlen(user) + 3]; /* + 3 for ": " and the \0 */ - snprintf(nprompt, sizeof nprompt, - "SUDO_PROMPT=[sudo] password for %s: ", user); - if (pam_putenv(pamh, nprompt) != PAM_SUCCESS) - _pam_log(pamh, LOG_NOTICE, "failed to set PAM sudo" - " prompt (%s)", nprompt); - } - + int retval; + retval = PAM_SUCCESS; return retval; } diff --git a/src/pam_radius_auth.h b/src/pam_radius_auth.h index 5e69b37..2b1e48d 100644 --- a/src/pam_radius_auth.h +++ b/src/pam_radius_auth.h @@ -20,6 +20,7 @@ #include <security/pam_ext.h> #include <stdarg.h> #include <utmp.h> +#include <pwd.h> #include <time.h> #include <netinet/in.h> #include <netdb.h> @@ -49,7 +50,7 @@ /* Defaults for the prompt option */ #define MAXPROMPT 33 /* max prompt length, including '\0' */ -#define DEFAULT_PROMPT "Password" /* default prompt, without the ': ' */ +#define DEFAULT_PROMPT "Password: " /* default prompt, */ /************************************************************************* * Platform specific defines @@ -127,26 +128,38 @@ typedef struct attribute_t { typedef struct radius_server_t { struct radius_server_t *next; struct sockaddr_storage ip_storage; + struct sockaddr_storage ipacct_storage; struct sockaddr *ip; + struct sockaddr *ip_acct; char *hostname; + char *hostpart; char *secret; + char *port; int timeout; - int accounting; + int sockfd; + int family; + char src_ip[MAX_IP_LEN]; } radius_server_t; typedef struct radius_conf_t { radius_server_t *server; + char *client_id; + CONST char *conf_file; int retries; int localifdown; - char *client_id; int accounting_bug; int force_prompt; int max_challenge; - int sockfd; - int sockfd6; int debug; - CONST char *conf_file; + int min_priv_lvl; char prompt[MAXPROMPT]; + char vrfname[64]; } radius_conf_t; +void __write_mapfile(pam_handle_t * p, const char *usr, uid_t uid, int priv, + int dbg); +int __remove_mapfile(pam_handle_t * pamh, const char *user, int dbg); +void __chk_homedir(pam_handle_t * p, const char *usr, const char *home, + int dbg); + #endif /* PAM_RADIUS_H */ diff --git a/src/radius.h b/src/radius.h index 2ee11dc..b826bbb 100644 --- a/src/radius.h +++ b/src/radius.h @@ -36,11 +36,11 @@ #define AUTH_STRING_LEN 128 /* maximum of 254 */ typedef struct pw_auth_hdr { - uint8_t code; - uint8_t id; - uint16_t length; - uint8_t vector[AUTH_VECTOR_LEN]; - uint8_t data[2]; + uint8_t code; + uint8_t id; + uint16_t length; + uint8_t vector[AUTH_VECTOR_LEN]; + uint8_t data[2]; } AUTH_HDR; #define AUTH_HDR_LEN 20 @@ -54,7 +54,6 @@ typedef struct pw_auth_hdr { #define PW_TYPE_IPADDR 2 #define PW_TYPE_DATE 3 - #define PW_AUTHENTICATION_REQUEST 1 #define PW_AUTHENTICATION_ACK 2 #define PW_AUTHENTICATION_REJECT 3 @@ -91,21 +90,21 @@ typedef struct pw_auth_hdr { #define PW_FRAMED_ROUTE 22 #define PW_FRAMED_IPXNET 23 #define PW_STATE 24 -#define PW_CLASS 25 /* string */ -#define PW_VENDOR_SPECIFIC 26 /* vendor */ -#define PW_SESSION_TIMEOUT 27 /* integer */ -#define PW_IDLE_TIMEOUT 28 /* integer */ -#define PW_TERMINATION_ACTION 29 /* integer */ -#define PW_CALLED_STATION_ID 30 /* string */ -#define PW_CALLING_STATION_ID 31 /* string */ -#define PW_NAS_IDENTIFIER 32 /* string */ -#define PW_PROXY_STATE 33 /* string */ -#define PW_LOGIN_LAT_SERVICE 34 /* string */ -#define PW_LOGIN_LAT_NODE 35 /* string */ -#define PW_LOGIN_LAT_GROUP 36 /* string */ -#define PW_FRAMED_APPLETALK_LINK 37 /* integer */ -#define PW_FRAMED_APPLETALK_NETWORK 38 /* integer */ -#define PW_FRAMED_APPLETALK_ZONE 39 /* string */ +#define PW_CLASS 25 /* string */ +#define PW_VENDOR_SPECIFIC 26 /* vendor */ +#define PW_SESSION_TIMEOUT 27 /* integer */ +#define PW_IDLE_TIMEOUT 28 /* integer */ +#define PW_TERMINATION_ACTION 29 /* integer */ +#define PW_CALLED_STATION_ID 30 /* string */ +#define PW_CALLING_STATION_ID 31 /* string */ +#define PW_NAS_IDENTIFIER 32 /* string */ +#define PW_PROXY_STATE 33 /* string */ +#define PW_LOGIN_LAT_SERVICE 34 /* string */ +#define PW_LOGIN_LAT_NODE 35 /* string */ +#define PW_LOGIN_LAT_GROUP 36 /* string */ +#define PW_FRAMED_APPLETALK_LINK 37 /* integer */ +#define PW_FRAMED_APPLETALK_NETWORK 38 /* integer */ +#define PW_FRAMED_APPLETALK_ZONE 39 /* string */ #define PW_ACCT_STATUS_TYPE 40 #define PW_ACCT_DELAY_TIME 41 @@ -115,11 +114,11 @@ typedef struct pw_auth_hdr { #define PW_ACCT_AUTHENTIC 45 #define PW_ACCT_SESSION_TIME 46 -#define PW_CHAP_CHALLENGE 60 /* string */ -#define PW_NAS_PORT_TYPE 61 /* integer */ -#define PW_PORT_LIMIT 62 /* integer */ -#define PW_LOGIN_LAT_PORT 63 /* string */ -#define PW_PROMPT 64 /* integer */ +#define PW_CHAP_CHALLENGE 60 /* string */ +#define PW_NAS_PORT_TYPE 61 /* integer */ +#define PW_PORT_LIMIT 62 /* integer */ +#define PW_LOGIN_LAT_PORT 63 /* string */ +#define PW_PROMPT 64 /* integer */ #define PW_NAS_IPV6_ADDRESS 95 /* octets */ @@ -189,39 +188,39 @@ typedef struct pw_auth_hdr { /* Server data structures */ typedef struct dict_attr { - char name[32]; - int value; - int type; - struct dict_attr *next; + char name[32]; + int value; + int type; + struct dict_attr *next; } DICT_ATTR; typedef struct dict_value { - char attrname[32]; - char name[32]; - int value; - struct dict_value *next; + char attrname[32]; + char name[32]; + int value; + struct dict_value *next; } DICT_VALUE; typedef struct value_pair { - char name[32]; - int attribute; - int type; - uint32_t lvalue; - char strvalue[AUTH_STRING_LEN]; - struct value_pair *next; + char name[32]; + int attribute; + int type; + uint32_t lvalue; + char strvalue[AUTH_STRING_LEN]; + struct value_pair *next; } VALUE_PAIR; typedef struct auth_req { - uint32_t ipaddr; - uint16_t udp_port; - uint8_t id; - uint8_t code; - uint8_t vector[16]; - uint8_t secret[16]; - VALUE_PAIR *request; - int child_pid; /* Process ID of child */ - uint32_t timestamp; - struct auth_req *next; /* Next active request */ + uint32_t ipaddr; + uint16_t udp_port; + uint8_t id; + uint8_t code; + uint8_t vector[16]; + uint8_t secret[16]; + VALUE_PAIR *request; + int child_pid; /* Process ID of child */ + uint32_t timestamp; + struct auth_req *next; /* Next active request */ } AUTH_REQ; #define SECONDS_PER_DAY 86400 @@ -229,4 +228,4 @@ typedef struct auth_req { #define CLEANUP_DELAY 5 #define MAX_REQUESTS 100 -#endif /* RADIUS_H */ +#endif /* RADIUS_H */ diff --git a/src/support.c b/src/support.c new file mode 100644 index 0000000..ed72f85 --- /dev/null +++ b/src/support.c @@ -0,0 +1,258 @@ +/* Copyright 2018 Cumulus Networks, Inc. All rights reserved. + * + * 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 - see the file COPYING. + */ + +/* + * support routines for Cumulus Linux RADIUS client support. + * They create the flat file mapping for the session, and create + * the home directory if needed. + * See the libnss-mapuser source for how the flat file database + * is used. + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <string.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <libaudit.h> +#include "pam_radius_auth.h" + +static const char mapdir[] = "/run/mapuser"; + +static unsigned get_sessionid(void) +{ + int fd = -1, cnt = 0; + unsigned id = 0U; + static char buf[12]; + + fd = open("/proc/self/sessionid", O_RDONLY); + if (fd != -1) { + cnt = read(fd, buf, sizeof(buf)); + close(fd); + } + if (fd != -1 && cnt > 0) { + id = strtoul(buf, NULL, 0); + } + return id; +} + +/* + * Write the mapping file used by libnss-mapuser into mapdir/SESSIONID + * This info is used by the mapuser and mapuid NSS plugins to return + * correct information for users that are logged in using mapping (that + * is, are not present in the password database(s)). + * This allows very simple configuration of RADIUS clients, since it's + * no longer necessary to add all user accounts to the local /etc/passwd + * file (or use LDAP, etc.). + */ +void +__write_mapfile(pam_handle_t * pamh, const char *user, uid_t uid, + int privileged, int debug) +{ + char tmstr[64], tmpstr[64]; + struct timeval tv = { 0, 0 }; + struct tm *tmv; + int res = 0; + unsigned session; + uid_t auid; + pid_t pid; + FILE *f; + + (void)gettimeofday(&tv, NULL); + tmv = localtime(&tv.tv_sec); + *tmstr = '\0'; + if (tmv) + res = strftime(tmpstr, sizeof tmpstr, "%FT%T", tmv); + + if (!res && !*tmstr) + snprintf(tmpstr, sizeof tmpstr, "%llu", + (unsigned long long)tv.tv_sec); + + snprintf(tmstr, sizeof tmstr, "%s.%u", tmpstr, (unsigned)tv.tv_usec); + + auid = audit_getloginuid(); + if (auid == ~0U) { /* normal case */ + audit_setloginuid(uid); + auid = audit_getloginuid(); + } + session = get_sessionid(); + pid = getpid(); + + if (auid == 0 || auid == ~0U || session == ~0U) { + /* if these aren't valid, we can't use the mapfile, so + * don't create it + */ + if (debug) + pam_syslog(pamh, LOG_DEBUG, "Skipping mapfile user=%s" + " auid=%u session=%u", user, auid, session); + return; + + } + + /* won't hurt if it already exists, no more overhead than stat() first */ + mkdir(mapdir, 0755); + snprintf(tmpstr, sizeof tmpstr, "%s/%u", mapdir, session); + /* + * Only create if it doesn't exist. It might exist if we are called from + * su or sudo after a login, for example + */ + f = fopen(tmpstr, "wx"); + if (!f) { + if (errno != EEXIST) + pam_syslog(pamh, LOG_WARNING, + "Can't create mapfile %s for user (%s): %m", + tmpstr, user); + return; + } + res = + fprintf(f, + "%s\nuser=%s\npid=%u\nauid=%u\nsession=%u\nprivileged=%s\n", + tmstr, user, pid, auid, session, privileged ? "yes" : "no"); + if (fclose(f) == EOF || res <= 0) + pam_syslog(pamh, LOG_WARNING, "Error writing mapfile %s for" + " user (%s): %m", tmpstr, user); +} + +/* + * Remove the mapping file used by libnss-mapuser into mapdir/SESSIONID + * based on the session. called from pam's sm_close entry point. + * return 0 if not removed, 1 if removed. This is so we can avoid + * talking to the RADIUS server if the close entry point isn't for + * one of our sessions. + */ +int __remove_mapfile(pam_handle_t * pamh, const char *user, int debug) +{ + unsigned session; + uid_t auid; + pid_t pid; + int auidmatch = 0, sessmatch = 0, pidmatch = 0, usermatch = 0; + char mapfile[64], linebuf[128]; + FILE *f; + + if (!user) + return 0; /* shouldn't ever happen */ + pid = getpid(); + session = get_sessionid(); + if (!session || session == (uid_t) - 1) + return 0; + snprintf(mapfile, sizeof mapfile, "%s/%u", mapdir, session); + f = fopen(mapfile, "r"); + if (!f) + return 0; + auid = audit_getloginuid(); + while (fgets(linebuf, sizeof linebuf, f)) { + unsigned long val; + char *ok; + if (!strncmp(linebuf, "session=", 8)) { + val = strtoul(linebuf + 8, &ok, 0); + if (val == session && ok != (linebuf + 8)) + sessmatch = 1; + } else if (!strncmp(linebuf, "user=", 5)) { + strtok(linebuf + 5, " \t\n\r\f"); + if (!strcmp(user, linebuf + 5)) + usermatch = 1; + } else if (!strncmp(linebuf, "auid=", 5)) { + val = strtoul(linebuf + 5, &ok, 0); + if (val == auid && ok != (linebuf + 5)) + auidmatch = 1; + } else if (!strncmp(linebuf, "pid=", 4)) { + val = strtoul(linebuf + 4, &ok, 0); + if (val == pid && ok != (linebuf + 4)) + pidmatch = 1; + } + } + fclose(f); + if (auidmatch && pidmatch && sessmatch && usermatch) { + if (unlink(mapfile)) + pam_syslog(pamh, LOG_WARNING, + "Remove mapfile %s for user %s failed: %m", + mapfile, user); + } + else if (debug) + pam_syslog(pamh, LOG_DEBUG, "mapfile %s user %s not removed," + " doesn't match", mapfile, user); + return 1; +} + +/* + * check to see if the home directory for the user exists + * and create it using the mkhomedir_helper if it does not. + * The code is based on the pam_mkhomedir plugin source + * It must be called after the mapping file is written, or + * getpwnam() won't be able to return the correct information + * using the libnss-mapuser plugin (which is what we expect + * for this RADIUS client). + */ +void +__chk_homedir(pam_handle_t * pamh, const char *user, const char *homedir, + int debug) +{ + int rc, retval, child, restore = 0; + struct stat st; + struct sigaction newsa, oldsa; + const char *path = "/sbin/mkhomedir_helper"; + + if (stat(homedir, &st) == 0) + return; + if (debug) + pam_syslog(pamh, LOG_NOTICE, + "creating home directory %s for user %s", homedir, + user); + + /* + * Ensure that when child process exits that the program using PAM + * doesn't get a signal it isn't expecting, which might kill the + * program, or confuse it. + */ + memset(&newsa, '\0', sizeof(newsa)); + newsa.sa_handler = SIG_DFL; + if (sigaction(SIGCHLD, &newsa, &oldsa) == 0) + restore = 1; + + child = fork(); + if (child == -1) { + pam_syslog(pamh, LOG_ERR, "fork to exec %s %s failed: %m", + path, user); + return; + } + if (child == 0) { + execl(path, path, user, NULL); + pam_syslog(pamh, LOG_ERR, "exec %s %s failed: %m", path, user); + exit(1); + } + + while ((rc = waitpid(child, &retval, 0)) < 0 && errno == EINTR) ; + if (rc < 0) + pam_syslog(pamh, LOG_ERR, + "waitpid for exec of %s %s failed: %m", path, user); + else if (!WIFEXITED(retval)) + pam_syslog(pamh, LOG_ERR, "%s %s abnormal exit: 0x%x", path, + user, retval); + else { + retval = WEXITSTATUS(retval); + if (retval) + pam_syslog(pamh, LOG_ERR, "%s %s abnormal exit: %d", + path, user, retval); + } + + if (restore) + sigaction(SIGCHLD, &oldsa, NULL); +} |