summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--debian/control4
-rw-r--r--debian/radius3
-rwxr-xr-xdebian/rules2
-rw-r--r--pam_radius_auth.522
-rw-r--r--pam_radius_auth.conf16
-rw-r--r--src/Makefile5
-rw-r--r--src/pam_radius_auth.c1078
-rw-r--r--src/pam_radius_auth.h25
-rw-r--r--src/radius.h101
-rw-r--r--src/support.c258
11 files changed, 982 insertions, 539 deletions
diff --git a/Makefile b/Makefile
index 753a75d..21027e0 100644
--- a/Makefile
+++ b/Makefile
@@ -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);
+}