diff --git a/lib/libradius/Makefile b/lib/libradius/Makefile index d71d8f2022ed..cb68eaa32b1a 100644 --- a/lib/libradius/Makefile +++ b/lib/libradius/Makefile @@ -35,6 +35,7 @@ MAN= libradius.3 radius.conf.5 MLINKS+=libradius.3 rad_acct_open.3 \ libradius.3 rad_add_server.3 \ + libradius.3 rad_add_server_ex.3 \ libradius.3 rad_auth_open.3 \ libradius.3 rad_bind_to.3 \ libradius.3 rad_close.3 \ diff --git a/lib/libradius/libradius.3 b/lib/libradius/libradius.3 index ae0ee2406032..253659cdc947 100644 --- a/lib/libradius/libradius.3 +++ b/lib/libradius/libradius.3 @@ -37,6 +37,8 @@ .Fn rad_acct_open "void" .Ft int .Fn rad_add_server "struct rad_handle *h" "const char *host" "int port" "const char *secret" "int timeout" "int max_tries" +.Ft int +.Fn rad_add_server_ex "struct rad_handle *h" "const char *host" "int port" "const char *secret" "int timeout" "int max_tries" "int dead_time" "struct in_addr bindto" .Ft "struct rad_handle *" .Fn rad_auth_open "void" .Ft void @@ -153,7 +155,12 @@ is used. returns 0 on success, or \-1 if an error occurs. .Pp The library can also be configured programmatically by calls to -.Fn rad_add_server . +.Fn rad_add_server +or +.Fn rad_add_server_ex . +.Fn rad_add_server +is a backward compatible function, implemented via +.Fn rad_add_server_ex . The .Fa host parameter specifies the server host, either as a fully qualified @@ -188,11 +195,20 @@ The maximum number of repeated requests to make before giving up is passed into the .Fa max_tries parameter. +Time interval in seconds when the server will not be requested +if it is marked as dead (did not answer on the last try) set with +.Fa dead_time +parameter. +.Fa bindto +parameter is an IP address on the multihomed host that is used as +a source address for all requests. .Fn rad_add_server returns 0 on success, or \-1 if an error occurs. .Pp .Fn rad_add_server -may be called multiple times, and it may be used together with +or +.Fn rad_add_server_ex +may be called multiple times, and they may be used together with .Fn rad_config . At most 10 servers may be specified. When multiple servers are given, they are tried in round-robin diff --git a/lib/libradius/radius.conf.5 b/lib/libradius/radius.conf.5 index 6ac84e07cc3f..6f89c3ae2df3 100644 --- a/lib/libradius/radius.conf.5 +++ b/lib/libradius/radius.conf.5 @@ -44,7 +44,7 @@ Leading white space is ignored, as are empty lines and lines containing only comments. .Pp -A RADIUS server is described by three to five fields on a line: +A RADIUS server is described by three to seven fields on a line: .Pp .Bl -item -offset indent -compact .It @@ -57,6 +57,10 @@ Shared secret Timeout .It Retries +.It +Dead time +.It +Bind address .El .Pp The fields are separated by white space. @@ -139,6 +143,13 @@ If omitted, it defaults to 3 attempts. Note, this is the total number of attempts and not the number of retries. .Pp +The sixth field contains a decimal integer specifying a time interval +in seconds when the server will not requested if it was inaccessible +on the last try. 0 means ask always. +.Pp +The seventh field contains an IP address on multihomed host. All +requests will be binded to this IP. +.Pp Up to 10 RADIUS servers may be specified for each service type. The servers are tried in round-robin fashion, until a valid response is received or the @@ -161,6 +172,9 @@ acct radius1.domain.com OurLittleSecret # timeout and maximum tries: auth auth.domain.com:1645 "I can't see you" 5 4 +# As above but set dead time and bind address +auth auth.domain.com:1645 "I can't see you" 5 4 60 192.168.1.8 + # A server specified by its IP address: auth 192.168.27.81 $X*#..38947ax-+= .Ed diff --git a/lib/libradius/radlib.c b/lib/libradius/radlib.c index 46a9b58c9e2f..0b67b0ffd90b 100644 --- a/lib/libradius/radlib.c +++ b/lib/libradius/radlib.c @@ -43,6 +43,8 @@ __FBSDID("$FreeBSD$"); #include #endif +#define MAX_FIELDS 7 + /* We need the MPPE_KEY_LEN define */ #include @@ -378,6 +380,18 @@ put_raw_attr(struct rad_handle *h, int type, const void *value, size_t len) int rad_add_server(struct rad_handle *h, const char *host, int port, const char *secret, int timeout, int tries) +{ + struct in_addr bindto; + bindto.s_addr = INADDR_ANY; + + return rad_add_server_ex(h, host, port, secret, timeout, tries, + DEAD_TIME, &bindto); +} + +int +rad_add_server_ex(struct rad_handle *h, const char *host, int port, + const char *secret, int timeout, int tries, int dead_time, + struct in_addr *bindto) { struct rad_server *srvp; @@ -421,6 +435,10 @@ rad_add_server(struct rad_handle *h, const char *host, int port, srvp->timeout = timeout; srvp->max_tries = tries; srvp->num_tries = 0; + srvp->is_dead = 0; + srvp->dead_time = dead_time; + srvp->next_probe = 0; + srvp->bindto = bindto->s_addr; h->num_servers++; return 0; } @@ -441,6 +459,13 @@ rad_close(struct rad_handle *h) free(h); } +void +rad_bind_to(struct rad_handle *h, in_addr_t addr) +{ + + h->bindto = addr; +} + int rad_config(struct rad_handle *h, const char *path) { @@ -468,11 +493,15 @@ rad_config(struct rad_handle *h, const char *path) char *secret; char *timeout_str; char *maxtries_str; + char *dead_time_str; + char *bindto_str; char *end; char *wanttype; unsigned long timeout; unsigned long maxtries; + unsigned long dead_time; int port; + struct in_addr bindto; int i; linenum++; @@ -491,7 +520,7 @@ rad_config(struct rad_handle *h, const char *path) buf[len - 1] = '\0'; /* Extract the fields from the line. */ - nfields = split(buf, fields, 5, msg, sizeof msg); + nfields = split(buf, fields, MAX_FIELDS, msg, sizeof msg); if (nfields == -1) { generr(h, "%s:%d: %s", path, linenum, msg); retval = -1; @@ -507,7 +536,7 @@ rad_config(struct rad_handle *h, const char *path) */ if (strcmp(fields[0], "auth") != 0 && strcmp(fields[0], "acct") != 0) { - if (nfields >= 5) { + if (nfields >= MAX_FIELDS) { generr(h, "%s:%d: invalid service type", path, linenum); retval = -1; @@ -529,6 +558,8 @@ rad_config(struct rad_handle *h, const char *path) secret = fields[2]; timeout_str = fields[3]; maxtries_str = fields[4]; + dead_time_str = fields[5]; + bindto_str = fields[6]; /* Ignore the line if it is for the wrong service type. */ wanttype = h->type == RADIUS_AUTH ? "auth" : "acct"; @@ -570,8 +601,30 @@ rad_config(struct rad_handle *h, const char *path) } else maxtries = MAXTRIES; - if (rad_add_server(h, host, port, secret, timeout, maxtries) == - -1) { + if (dead_time_str != NULL) { + dead_time = strtoul(dead_time_str, &end, 10); + if (*end != '\0') { + generr(h, "%s:%d: invalid dead_time", path, + linenum); + retval = -1; + break; + } + } else + dead_time = DEAD_TIME; + + if (bindto_str != NULL) { + bindto.s_addr = inet_addr(bindto_str); + if (bindto.s_addr == INADDR_NONE) { + generr(h, "%s:%d: invalid bindto", path, + linenum); + retval = -1; + break; + } + } else + bindto.s_addr = INADDR_ANY; + + if (rad_add_server_ex(h, host, port, secret, timeout, maxtries, + dead_time, &bindto) == -1) { strcpy(msg, h->errmsg); generr(h, "%s:%d: %s", path, linenum, msg); retval = -1; @@ -596,7 +649,9 @@ int rad_continue_send_request(struct rad_handle *h, int selected, int *fd, struct timeval *tv) { - int n; + int n, cur_srv; + time_t now; + struct sockaddr_in sin; if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); @@ -621,19 +676,61 @@ rad_continue_send_request(struct rad_handle *h, int selected, int *fd, } } - if (h->try == h->total_tries) { - generr(h, "No valid RADIUS responses received"); - return -1; - } - /* * Scan round-robin to the next server that has some * tries left. There is guaranteed to be one, or we * would have exited this loop by now. */ - while (h->servers[h->srv].num_tries >= h->servers[h->srv].max_tries) - if (++h->srv >= h->num_servers) - h->srv = 0; + cur_srv = h->srv; + now = time(NULL); + if (h->servers[h->srv].num_tries >= h->servers[h->srv].max_tries) { + /* Set next probe time for this server */ + if (h->servers[h->srv].dead_time) { + h->servers[h->srv].is_dead = 1; + h->servers[h->srv].next_probe = now + + h->servers[h->srv].dead_time; + } + do { + h->srv++; + if (h->srv >= h->num_servers) + h->srv = 0; + if (h->servers[h->srv].is_dead == 0) + break; + if (h->servers[h->srv].dead_time && + h->servers[h->srv].next_probe <= now) { + h->servers[h->srv].is_dead = 0; + h->servers[h->srv].num_tries = 0; + break; + } + } while (h->srv != cur_srv); + + if (h->srv == cur_srv) { + generr(h, "No valid RADIUS responses received"); + return (-1); + } + } + + /* Rebind */ + if (h->bindto != h->servers[h->srv].bindto) { + h->bindto = h->servers[h->srv].bindto; + close(h->fd); + if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { + generr(h, "Cannot create socket: %s", strerror(errno)); + return -1; + } + memset(&sin, 0, sizeof sin); + sin.sin_len = sizeof sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = h->bindto; + sin.sin_port = 0; + if (bind(h->fd, (const struct sockaddr *)&sin, + sizeof sin) == -1) { + generr(h, "bind: %s", strerror(errno)); + close(h->fd); + h->fd = -1; + return (-1); + } + } if (h->out[POS_CODE] == RAD_ACCESS_REQUEST) { /* Insert the scrambled password into the request */ @@ -641,9 +738,11 @@ rad_continue_send_request(struct rad_handle *h, int selected, int *fd, insert_scrambled_password(h, h->srv); } insert_message_authenticator(h, 0); + if (h->out[POS_CODE] != RAD_ACCESS_REQUEST) { /* Insert the request authenticator into the request */ - insert_request_authenticator(h, h->srv); + memset(&h->out[POS_AUTH], 0, LEN_AUTH); + insert_request_authenticator(h, 0); } /* Send the request */ @@ -654,7 +753,6 @@ rad_continue_send_request(struct rad_handle *h, int selected, int *fd, tv->tv_sec = 1; /* Do not wait full timeout if send failed. */ else tv->tv_sec = h->servers[h->srv].timeout; - h->try++; h->servers[h->srv].num_tries++; tv->tv_usec = 0; *fd = h->fd; @@ -740,6 +838,10 @@ rad_create_request(struct rad_handle *h, int code) generr(h, "denied function call"); return (-1); } + if (h->num_servers == 0) { + generr(h, "No RADIUS servers specified"); + return (-1); + } h->out[POS_CODE] = code; h->out[POS_IDENT] = ++h->ident; if (code == RAD_ACCESS_REQUEST) { @@ -756,16 +858,9 @@ rad_create_request(struct rad_handle *h, int code) clear_password(h); h->authentic_pos = 0; h->out_created = 1; - h->bindto = INADDR_ANY; return 0; } -void -rad_bind_to(struct rad_handle *h, in_addr_t addr) -{ - h->bindto = addr; -} - int rad_create_response(struct rad_handle *h, int code) { @@ -793,6 +888,15 @@ rad_cvt_addr(const void *data) return value; } +struct in6_addr +rad_cvt_addr6(const void *data) +{ + struct in6_addr value; + + memcpy(&value.s6_addr, data, sizeof value.s6_addr); + return value; +} + u_int32_t rad_cvt_int(const void *data) { @@ -848,6 +952,8 @@ int rad_init_send_request(struct rad_handle *h, int *fd, struct timeval *tv) { int srv; + time_t now; + struct sockaddr_in sin; if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); @@ -855,8 +961,6 @@ rad_init_send_request(struct rad_handle *h, int *fd, struct timeval *tv) } /* Make sure we have a socket to use */ if (h->fd == -1) { - struct sockaddr_in sin; - if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { generr(h, "Cannot create socket: %s", strerror(errno)); return -1; @@ -902,21 +1006,30 @@ rad_init_send_request(struct rad_handle *h, int *fd, struct timeval *tv) h->out[POS_LENGTH] = h->out_len >> 8; h->out[POS_LENGTH+1] = h->out_len; - /* - * Count the total number of tries we will make, and zero the - * counter for each server. - */ - h->total_tries = 0; - for (srv = 0; srv < h->num_servers; srv++) { - h->total_tries += h->servers[srv].max_tries; + h->srv = 0; + now = time(NULL); + for (srv = 0; srv < h->num_servers; srv++) h->servers[srv].num_tries = 0; - } - if (h->total_tries == 0) { - generr(h, "No RADIUS servers specified"); - return -1; + /* Find a first good server. */ + for (srv = 0; srv < h->num_servers; srv++) { + if (h->servers[srv].is_dead == 0) + break; + if (h->servers[srv].dead_time && + h->servers[srv].next_probe <= now) { + h->servers[srv].is_dead = 0; + break; + } + h->srv++; } - h->try = h->srv = 0; + /* If all servers was dead on the last probe, try from beginning */ + if (h->srv == h->num_servers) { + for (srv = 0; srv < h->num_servers; srv++) { + h->servers[srv].is_dead = 0; + h->servers[srv].next_probe = 0; + } + h->srv = 0; + } return rad_continue_send_request(h, 0, fd, tv); } @@ -946,6 +1059,7 @@ rad_auth_open(void) h->type = RADIUS_AUTH; h->out_created = 0; h->eap_msg = 0; + h->bindto = INADDR_ANY; } return h; } @@ -986,6 +1100,13 @@ rad_put_addr(struct rad_handle *h, int type, struct in_addr addr) return rad_put_attr(h, type, &addr.s_addr, sizeof addr.s_addr); } +int +rad_put_addr6(struct rad_handle *h, int type, struct in6_addr addr) +{ + + return rad_put_attr(h, type, &addr.s6_addr, sizeof addr.s6_addr); +} + int rad_put_attr(struct rad_handle *h, int type, const void *value, size_t len) { @@ -1228,6 +1349,15 @@ rad_put_vendor_addr(struct rad_handle *h, int vendor, int type, sizeof addr.s_addr)); } +int +rad_put_vendor_addr6(struct rad_handle *h, int vendor, int type, + struct in6_addr addr) +{ + + return (rad_put_vendor_attr(h, vendor, type, &addr.s6_addr, + sizeof addr.s6_addr)); +} + int rad_put_vendor_attr(struct rad_handle *h, int vendor, int type, const void *value, size_t len) diff --git a/lib/libradius/radlib.h b/lib/libradius/radlib.h index f9e451b465d9..a47931878922 100644 --- a/lib/libradius/radlib.h +++ b/lib/libradius/radlib.h @@ -194,6 +194,9 @@ __BEGIN_DECLS struct rad_handle *rad_acct_open(void); int rad_add_server(struct rad_handle *, const char *, int, const char *, int, int); +int rad_add_server_ex(struct rad_handle *, + const char *, int, const char *, int, int, + int, struct in_addr *); struct rad_handle *rad_auth_open(void); void rad_bind_to(struct rad_handle *, in_addr_t); void rad_close(struct rad_handle *); @@ -203,6 +206,7 @@ int rad_continue_send_request(struct rad_handle *, int, int rad_create_request(struct rad_handle *, int); int rad_create_response(struct rad_handle *, int); struct in_addr rad_cvt_addr(const void *); +struct in6_addr rad_cvt_addr6(const void *); u_int32_t rad_cvt_int(const void *); char *rad_cvt_string(const void *, size_t); int rad_get_attr(struct rad_handle *, const void **, @@ -211,6 +215,7 @@ int rad_init_send_request(struct rad_handle *, int *, struct timeval *); struct rad_handle *rad_open(void); /* Deprecated, == rad_auth_open */ int rad_put_addr(struct rad_handle *, int, struct in_addr); +int rad_put_addr6(struct rad_handle *, int, struct in6_addr); int rad_put_attr(struct rad_handle *, int, const void *, size_t); int rad_put_int(struct rad_handle *, int, u_int32_t); diff --git a/lib/libradius/radlib_private.h b/lib/libradius/radlib_private.h index a76e594aa613..bfbbbd17ca89 100644 --- a/lib/libradius/radlib_private.h +++ b/lib/libradius/radlib_private.h @@ -46,6 +46,7 @@ #define RADIUS_PORT 1812 #define RADACCT_PORT 1813 #define TIMEOUT 3 /* In seconds */ +#define DEAD_TIME 0 /* Limits */ #define ERRSIZE 128 /* Maximum error message length */ @@ -68,6 +69,10 @@ struct rad_server { int timeout; /* Timeout in seconds */ int max_tries; /* Number of tries before giving up */ int num_tries; /* Number of tries so far */ + int is_dead; /* The server did not answer last time */ + time_t dead_time; /* Don't try this server for the time period if it is dead */ + time_t next_probe; /* Time of a next probe after failure */ + in_addr_t bindto; /* Bind to address */ }; struct rad_handle { @@ -88,11 +93,9 @@ struct rad_handle { unsigned char in[MSGSIZE]; /* Response received */ int in_len; /* Length of response */ int in_pos; /* Current position scanning attrs */ - int total_tries; /* How many requests we'll send */ - int try; /* How many requests we've sent */ int srv; /* Server number we did last */ int type; /* Handle type */ - in_addr_t bindto; /* Bind to address */ + in_addr_t bindto; /* Current bind address */ }; struct vendor_attribute { diff --git a/lib/libradius/radlib_vs.h b/lib/libradius/radlib_vs.h index 8b3a75e79d6a..0acfb0e10e00 100644 --- a/lib/libradius/radlib_vs.h +++ b/lib/libradius/radlib_vs.h @@ -73,6 +73,7 @@ struct rad_handle; __BEGIN_DECLS int rad_get_vendor_attr(u_int32_t *, const void **, size_t *); int rad_put_vendor_addr(struct rad_handle *, int, int, struct in_addr); +int rad_put_vendor_addr6(struct rad_handle *, int, int, struct in6_addr); int rad_put_vendor_attr(struct rad_handle *, int, int, const void *, size_t); int rad_put_vendor_int(struct rad_handle *, int, int, u_int32_t);