summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pam_radius_auth.conf11
-rw-r--r--src/pam_radius_auth.c281
-rw-r--r--src/pam_radius_auth.h5
-rw-r--r--src/radius.h2
4 files changed, 167 insertions, 132 deletions
diff --git a/pam_radius_auth.conf b/pam_radius_auth.conf
index 576f1f3..2fe1f34 100644
--- a/pam_radius_auth.conf
+++ b/pam_radius_auth.conf
@@ -14,6 +14,9 @@
# "radius", and is looked up from /etc/services The timeout field is
# optional. The default timeout is 3 seconds.
#
+# For IPv6 literal addresses, the address has to be surrounded by
+# square brackets as usual. E.g. [2001:0db8:85a3::4].
+#
# If multiple RADIUS server lines exist, they are tried in order. The
# first server to return success or failure causes the module to return
# success or failure. Only if a server fails to response is it skipped,
@@ -22,10 +25,10 @@
# The timeout field controls how many seconds the module waits before
# deciding that the server has failed to respond.
#
-# server[:port] shared_secret timeout (s)
-127.0.0.1 secret 1
-other-server other-secret 3
-
+# server[:port] shared_secret timeout (s)
+127.0.0.1 secret 1
+other-server other-secret 3
+[2001:0db8:85a3::4]:1812 other6-secret 1
#
# having localhost in your radius configuration is a Good Thing.
#
diff --git a/src/pam_radius_auth.c b/src/pam_radius_auth.c
index 6e2c5aa..a9fd518 100644
--- a/src/pam_radius_auth.c
+++ b/src/pam_radius_auth.c
@@ -165,105 +165,70 @@ static void get_error_string(int errnum, char *buf, size_t buflen) {
}
/*
- * Return an IP address in host long notation from a host
- * name or address in dot notation.
+ * Return an IP address as a struct sockaddr *.
*/
-static uint32_t get_ipaddr(char *host) {
+static int get_ipaddr(char *host, struct sockaddr *addr, char *port) {
struct addrinfo hints;
struct addrinfo *results;
- uint32_t addr;
+ int r;
memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
+ hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_ADDRCONFIG;
- if (getaddrinfo(host, NULL, &hints, &results) == 0) {
- struct sockaddr_in *sockaddr = (struct sockaddr_in *)results->ai_addr;
- addr = ntohl(sockaddr->sin_addr.s_addr);
+ r = getaddrinfo(host, port && port[0] ? port : NULL, &hints, &results);
+ if (r == 0) {
+ memcpy(addr, results->ai_addr, results->ai_addrlen);
freeaddrinfo(results);
- } else {
- addr = (uint32_t)0;
}
- return addr;
+ return r;
}
/*
- * Gets the UDP port number associated with a service name.
- * The port number is returned in network byte order.
- */
-static uint16_t get_udp_port(char *service) {
- struct addrinfo hints;
- struct addrinfo *results;
- uint16_t port;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- hints.ai_socktype = SOCK_DGRAM;
-
- if (getaddrinfo(NULL, service, &hints, &results) == 0) {
- struct sockaddr_in *sockaddr = (struct sockaddr_in *)results->ai_addr;
- port = sockaddr->sin_port;
- freeaddrinfo(results);
- } else {
- port = (uint16_t)0;
- }
-
- return port;
-}
-
-/*
- * take server->hostname, and convert it to server->ip and server->port
+ * take server->hostname, and convert it to server->ip
*/
static int host2server(int debug, radius_server_t *server)
{
- char *p;
-
- if ((p = strchr(server->hostname, ':')) != NULL) {
- *(p++) = '\0'; /* split the port off from the host name */
- }
-
- if ((server->ip.s_addr = get_ipaddr(server->hostname)) == ((uint32_t)0)) {
- DPRINT(LOG_DEBUG, "DEBUG: get_ipaddr(%s) returned 0.\n", server->hostname);
- return PAM_AUTHINFO_UNAVAIL;
+ char hostbuffer[256];
+ char tmp[256];
+ char *hostname;
+ char *portstart;
+ char *p, *port;
+ int r, n;
+
+ /* hostname might be [ipv6::address] */
+ strncpy(hostbuffer, server->hostname, sizeof(hostbuffer) - 1);
+ hostbuffer[sizeof(hostbuffer) - 1] = 0;
+ hostname = hostbuffer;
+ portstart = hostbuffer;
+ if (hostname[0] == '[') {
+ if ((p = strchr(hostname, ']')) != NULL) {
+ hostname++;
+ *p++ = 0;
+ portstart = p;
+ }
}
-
- /*
- * If the server port hasn't already been defined, go get it.
- */
- if (!server->port) {
- if (p && isdigit((unsigned char)*p)) { /* the port looks like it's a number */
- unsigned int i = atoi(p) & 0xffff;
-
- if (!server->accounting) {
- server->port = htons((uint16_t) i);
- } else {
- server->port = htons((uint16_t) (i + 1));
- }
- } else { /* the port looks like it's a name */
- if (p) { /* maybe it's not "radius" */
- server->port = get_udp_port(p);
- /* quotes allow distinction from above, lest p be radius or radacct */
- DPRINT(LOG_DEBUG, "DEBUG: get_udp_port('%s') returned %u.\n", p, server->port);
- *(--p) = ':'; /* be sure to put the delimiter back */
- } else {
- if (!server->accounting) {
- server->port = get_udp_port("radius");
- DPRINT(LOG_DEBUG, "DEBUG: get_udp_port(radius) returned %u.\n", server->port);
- } else {
- server->port = get_udp_port("radacct");
- DPRINT(LOG_DEBUG, "DEBUG: get_udp_port(radacct) returned %u.\n", server->port);
- }
- }
-
- if (!server->port) {
- /* debugging above... */
- return PAM_AUTHINFO_UNAVAIL;
+ if ((port = strchr(portstart, ':')) != NULL) {
+ *port++ = '\0';
+ if (isdigit((unsigned char)*port) && server->accounting) {
+ if (sscanf(port, "%d", &n) == 1) {
+ snprintf(tmp, sizeof(tmp), "%d", n + 1);
+ port = tmp;
}
}
+ } else {
+ if (server->accounting)
+ port = "radacct";
+ else
+ port = "radius";
}
- return PAM_SUCCESS;
+ server->ip = (struct sockaddr *)&server->ip_storage;
+ r = get_ipaddr(hostname, server->ip, port);
+ DPRINT(LOG_DEBUG, "DEBUG: get_ipaddr(%s) returned %d.\n", hostname, r);
+ return r;
}
/*
@@ -434,6 +399,37 @@ static void add_int_attribute(AUTH_HDR *request, unsigned char type, int data)
add_attribute(request, type, (unsigned char *) &value, sizeof(int));
}
+static void add_nas_ip_address(AUTH_HDR *request, char *hostname) {
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ int v4seen = 0, v6seen = 0;
+ int r;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ r = getaddrinfo(hostname, NULL, &hints, &ai);
+ if (r != 0)
+ return;
+
+ while (ai != NULL) {
+ if (!v4seen && ai->ai_family == AF_INET) {
+ v4seen = 1;
+ 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->ai_next;
+ }
+ freeaddrinfo(ai);
+}
+
/*
* Add a RADIUS password attribute to the packet. Some magic is done here.
*
@@ -524,7 +520,9 @@ static void cleanup(radius_server_t *server)
*/
static int initialize(radius_conf_t *conf, int accounting)
{
- struct sockaddr salocal;
+ struct sockaddr_storage salocal;
+ struct sockaddr_storage salocal4;
+ struct sockaddr_storage salocal6;
char hostname[BUFFER_SIZE];
char secret[BUFFER_SIZE];
@@ -532,10 +530,15 @@ static int initialize(radius_conf_t *conf, int accounting)
char *p;
FILE *fserver;
radius_server_t *server = NULL;
- struct sockaddr_in * s_in;
int timeout;
int line = 0;
char src_ip[MAX_IP_LEN];
+ int seen_v6 = 0;
+
+ memset(&salocal4, 0, sizeof(salocal4));
+ memset(&salocal6, 0, sizeof(salocal6));
+ ((struct sockaddr *)&salocal4)->sa_family = AF_INET;
+ ((struct sockaddr *)&salocal6)->sa_family = AF_INET6;
/* the first time around, read the configuration file */
if ((fserver = fopen (conf->conf_file, "r")) == (FILE*)NULL) {
@@ -591,7 +594,6 @@ static int initialize(radius_conf_t *conf, int accounting)
server->hostname = strdup(hostname);
server->secret = strdup(secret);
server->accounting = accounting;
- server->port = 0;
if ((timeout < 1) || (timeout > 60)) {
server->timeout = 3;
@@ -599,6 +601,20 @@ static int initialize(radius_conf_t *conf, int accounting)
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);
@@ -609,6 +625,11 @@ static int initialize(radius_conf_t *conf, int accounting)
return PAM_AUTHINFO_UNAVAIL;
}
+ /*
+ * FIXME- we could have different source-ips for different servers, so
+ * sockfd should probably be in the server struct, not in the conf struct.
+ */
+
/* open a socket. Dies if it fails */
conf->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (conf->sockfd < 0) {
@@ -627,24 +648,43 @@ static int initialize(radius_conf_t *conf, int accounting)
#endif
/* set up the local end of the socket communications */
- s_in = (struct sockaddr_in *) &salocal;
- memset ((char *) s_in, '\0', sizeof(struct sockaddr));
- s_in->sin_family = AF_INET;
- if (!*src_ip) {
- s_in->sin_addr.s_addr = INADDR_ANY;
- } else {
- if (!inet_aton(src_ip, (struct in_addr *) &(s_in->sin_addr.s_addr))) s_in->sin_addr.s_addr = INADDR_ANY;
+ 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(LOG_ERR, "Failed binding to port: %s", error_string);
+ close(conf->sockfd);
+ return PAM_AUTHINFO_UNAVAIL;
}
- s_in->sin_port = 0;
-
- if (bind(conf->sockfd, &salocal, sizeof (struct sockaddr_in)) < 0) {
+ /* 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(LOG_ERR, "Failed binding to port: %s", error_string);
+ _pam_log(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(LOG_ERR, "Unusable socket, FD is larger than %d\n", FD_SETSIZE);
+ close(conf->sockfd);
+ close(conf->sockfd6);
+ return PAM_AUTHINFO_UNAVAIL;
+ }
+#endif
+
+ /* 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(LOG_ERR, "Failed binding to IPv6 port: %s", error_string);
+ close(conf->sockfd);
+ close(conf->sockfd6);
+ return PAM_AUTHINFO_UNAVAIL;
+ }
return PAM_SUCCESS;
}
@@ -656,7 +696,6 @@ static int initialize(radius_conf_t *conf, int accounting)
static void build_radius_packet(AUTH_HDR *request, CONST char *user, CONST char *password, radius_conf_t *conf)
{
char hostname[256];
- uint32_t ipaddr;
hostname[0] = '\0';
gethostname(hostname, sizeof(hostname) - 1);
@@ -682,17 +721,8 @@ static void build_radius_packet(AUTH_HDR *request, CONST char *user, CONST char
add_password(request, PW_PASSWORD, "", conf->server->secret);
}
- /* the packet is from localhost if on localhost, to make configs easier */
- if ((conf->server->ip.s_addr == ntohl(0x7f000001)) || (!hostname[0])) {
- ipaddr = 0x7f000001;
- } else {
- ipaddr = get_ipaddr(hostname);
- }
-
- /* If we can't find an IP address, then don't add one */
- if (ipaddr) {
- add_int_attribute(request, PW_NAS_IP_ADDRESS, ipaddr);
- }
+ /* Perhaps add NAS IP Address (and v6 version) */
+ add_nas_ip_address(request, hostname);
/* There's always a NAS identifier */
if (conf->client_id && *conf->client_id) {
@@ -715,7 +745,6 @@ static void build_radius_packet(AUTH_HDR *request, CONST char *user, CONST char
static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *response,
char *password, char *old_password, int tries)
{
- socklen_t salen;
int total_length;
#ifdef HAVE_POLL_H
struct pollfd pollfds[1];
@@ -726,12 +755,11 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *respons
time_t now, end;
int rcode;
- struct sockaddr saremote;
- struct sockaddr_in *s_in = (struct sockaddr_in *) &saremote;
radius_server_t *server = conf->server;
int ok;
int server_tries;
int retval;
+ int sockfd;
/* ************************************************************ */
/* Now that we're done building the request, we can send it */
@@ -752,30 +780,26 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *respons
memset(response, 0, sizeof(AUTH_HDR));
/* only look up IP information as necessary */
- if ((retval = host2server(conf->debug, server)) != PAM_SUCCESS) {
+ retval = host2server(conf->debug, server);
+ if (retval != 0) {
_pam_log(LOG_ERR,
- "Failed looking up IP address for RADIUS server %s (errcode=%d)",
- server->hostname, retval);
+ "Failed looking up IP address for RADIUS server %s (error=%s)",
+ server->hostname, gai_strerror(retval));
ok = FALSE;
goto next; /* skip to the next server */
}
- /* set up per-server IP && port configuration */
- memset ((char *) s_in, '\0', sizeof(struct sockaddr));
- s_in->sin_family = AF_INET;
- s_in->sin_addr.s_addr = htonl(server->ip.s_addr);
- s_in->sin_port = server->port;
- total_length = ntohs(request->length);
-
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(conf->sockfd, (char *) request, total_length, 0,
- &saremote, sizeof(struct sockaddr_in)) < 0) {
+ 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(LOG_ERR, "Error sending RADIUS packet to server %s: %s",
@@ -786,7 +810,6 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *respons
/* ************************************************************ */
/* Wait for the response, and verify it. */
- salen = sizeof(struct sockaddr);
time(&now);
tv.tv_sec = server->timeout; /* wait for the specified time */
@@ -794,11 +817,11 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *respons
end = now + tv.tv_sec;
#ifdef HAVE_POLL_H
- pollfds[0].fd = conf->sockfd; /* wait only for the RADIUS UDP socket */
+ pollfds[0].fd = sockfd; /* wait only for the RADIUS UDP socket */
pollfds[0].events = POLLIN; /* wait for data to read */
#else
FD_ZERO(&set); /* clear out the set */
- FD_SET(conf->sockfd, &set); /* wait only for the RADIUS UDP socket */
+ FD_SET(sockfd, &set); /* wait only for the RADIUS UDP socket */
#endif
/* loop, waiting for the network to return data */
@@ -807,7 +830,7 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *respons
#ifdef HAVE_POLL_H
rcode = poll((struct pollfd *) &pollfds, 1, tv.tv_sec * 1000);
#else
- rcode = select(conf->sockfd + 1, &set, NULL, NULL, &tv);
+ rcode = select(sockfd + 1, &set, NULL, NULL, &tv);
#endif
/* timed out */
@@ -849,12 +872,12 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *respons
#ifdef HAVE_POLL_H
} else if (pollfds[0].revents & POLLIN) {
#else
- } else if (FD_ISSET(conf->sockfd, &set)) {
+ } else if (FD_ISSET(sockfd, &set)) {
#endif
/* try to receive some data */
- if ((total_length = recvfrom(conf->sockfd, (void *) response, BUFFER_SIZE,
- 0, &saremote, &salen)) < 0) {
+ 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(LOG_ERR, "error reading RADIUS packet from server %s: %s",
@@ -1240,6 +1263,8 @@ do_next:
DPRINT(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);
@@ -1366,6 +1391,8 @@ static int pam_private_session(pam_handle_t *pamh, int flags, int argc, CONST ch
error:
close(config.sockfd);
+ if (config.sockfd6 >= 0)
+ close(config.sockfd6);
cleanup(config.server);
return retval;
@@ -1600,6 +1627,8 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, CONST c
}
close(config.sockfd);
+ if (config.sockfd6 >= 0)
+ close(config.sockfd6);
cleanup(config.server);
_pam_forget(password);
diff --git a/src/pam_radius_auth.h b/src/pam_radius_auth.h
index b3f7839..887b8ad 100644
--- a/src/pam_radius_auth.h
+++ b/src/pam_radius_auth.h
@@ -130,8 +130,8 @@ typedef struct attribute_t {
typedef struct radius_server_t {
struct radius_server_t *next;
- struct in_addr ip;
- uint16_t port;
+ struct sockaddr_storage ip_storage;
+ struct sockaddr *ip;
char *hostname;
char *secret;
int timeout;
@@ -147,6 +147,7 @@ typedef struct radius_conf_t {
int force_prompt;
int max_challenge;
int sockfd;
+ int sockfd6;
int debug;
CONST char *conf_file;
char prompt[MAXPROMPT];
diff --git a/src/radius.h b/src/radius.h
index bcfe547..2ee11dc 100644
--- a/src/radius.h
+++ b/src/radius.h
@@ -121,6 +121,8 @@ typedef struct pw_auth_hdr {
#define PW_LOGIN_LAT_PORT 63 /* string */
#define PW_PROMPT 64 /* integer */
+#define PW_NAS_IPV6_ADDRESS 95 /* octets */
+
/*
* INTEGER TRANSLATIONS
*/