summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Olson <olson@cumulusnetworks.com>2018-02-27 19:07:33 -0800
committerDave Olson <olson@cumulusnetworks.com>2018-04-02 12:22:31 -0700
commit55011d779684c0e34a60768ede7967c36f2753ff (patch)
tree2d24bc865a04ebfa1a4236dc64be9d782c2967dd
parent96df95a2e72bf260bdc294e77276452229e13cf1 (diff)
downloadlibpam-radius-auth-55011d779684c0e34a60768ede7967c36f2753ff.tar.gz
libpam-radius-auth-55011d779684c0e34a60768ede7967c36f2753ff.zip
Add limited support for privileges (VSA shell:priv-lvl=15)
Ticket: CM-19457 Reviewed By: Testing Done: As with the tacplus client, we'll support priv-lvl=15 as a privileged user, able to run config commands and sudo (when used with libnss-mapuser). Added new code to decode VSA attributes, and search for shell:priv-lvl=#. A new config item is added "priv-lvl" in the configuration file to specify the minimum value to be considered privileged. The default is 15. Writing mapping session file in the plugin now, because it needs to be present for the final getpw* calls from ssh, login, etc. Dropped the homedir in the mapfile, we not ready to get it via NSS when we write the mapfile, and it wasn't ever used. Also added same pam condition as tacplus, don't invoke pam_radius_auth unless uid > 1000, to avoid overhead on system users and cumulus account, although that won't help as much as with tacplus, given the mappings. Also added copyrights to the pam header file Fixed a bunch of issues, which meant some significant restructuring. src_ip (as noted in some comments) really should have been in the server struct. Having done that, we don't need to open both v4 and v6 sockets, we only open the one we need after moving host2server() call into the initialization code. Only parse the pam_radius_auth.conf config file once (unless the PAM line specifies a different config file from previous pam mode, or the config file has changed). As part of that, do all the host name resolution up front, and store ip_acct for accounting port, as well as the previous ip for auth port. While doing that, set it up so initialization and the config file parsing are only done once in the common case. If the config file is specified on the pam command line, and it's different, then we'll re-open and re-initialize. That also means we normally only open the socket and bind once. Cleanup is now done via registering a pam_set_data() handler for the server list. Since the _pam_end() call may happen late, also ensure that all the sockets are marked close on exec. Fixed some white space and line length issues. Really should have been a separate commit, but... Document how port for accounting is derived, and changed it to use radacct if a named port was specified that isn't "radius" while warning about it.
-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);
+}