Index: libradius.3 =================================================================== --- libradius.3 +++ libradius.3 @@ -39,6 +39,8 @@ .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 int +.Fn rad_add_server_bindto "struct rad_handle *h" "const char *host" "int port" "const char *secret" "int timeout" "int max_tries" "int dead_time" "const struct sockaddr *bindto" .Ft "struct rad_handle *" .Fn rad_auth_open "void" .Ft void @@ -95,6 +97,8 @@ .Fn rad_server_secret "struct rad_handle *h" .Ft "void" .Fn rad_bind_to "struct rad_handle *h" "in_addr_t addr" +.Ft "void" +.Fn rad_set_bindto "struct rad_handle *h" "const struct sockaddr *addr" .Ft u_char * .Fn rad_demangle "struct rad_handle *h" "const void *mangled" "size_t mlen" .Ft u_char * @@ -155,16 +159,37 @@ 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 , +.Fn rad_add_server_ex or -.Fn rad_add_server_ex . +.Fn rad_add_server_bindto . .Fn rad_add_server -is a backward compatible function, implemented via -.Fn rad_add_server_ex . +and +.Fn rad_add_server_ex +are backward compatible functions that support only IPv4 protocol and are +implemented via +.Fn rad_add_server_bindto +function. The .Fa host parameter specifies the server host, either as a fully qualified -domain name or as a dotted-quad IP address in text form. +domain name or as a IP address in text form (both IPv4 and IPv6). +If the +.Fa host +resolves to multiple IP addresses, the +.Fn rad_add_server_bindto +function will create multiple server structures. +If the +.Fa bindto +argument is +.No non- Ns Dv NULL , +the function will only lookup DNS entries in the same protocol family as the +.Fa bindto . +If the +.Fa bindto +argument is +.Dv NULL , +the function will only use both IPv4 and IPv6 entries. The .Fa port parameter specifies the UDP port to contact on the server. @@ -202,12 +227,17 @@ .Fa bindto parameter is an IP address on the multihomed host that is used as a source address for all requests. +The +.Fa bindto +parameter can be +.Dv NULL . .Fn rad_add_server returns 0 on success, or \-1 if an error occurs. .Pp -.Fn rad_add_server -or +.Fn rad_add_server , .Fn rad_add_server_ex +and +.Fn rad_add_server_bindto may be called multiple times, and they may be used together with .Fn rad_config . At most 10 servers may be specified. @@ -218,8 +248,7 @@ .Ss Creating a RADIUS Request A RADIUS request consists of a code specifying the kind of request, and zero or more attributes which provide additional information. -To -begin constructing a new request, call +To begin constructing a new request, call .Fn rad_create_request . In addition to the usual .Vt "struct rad_handle *" , @@ -446,8 +475,13 @@ returns the secret shared with the current RADIUS server according to the supplied rad_handle. .Pp +.Fn rad_bind_to +is a backward compatible function that supports only IPv4 protocol and is +implemented via +.Fn rad_set_bindto +function. The -.Fn rad_bind_to +.Fn rad_set_bindto assigns a source address for all requests to the current RADIUS server. .Pp The @@ -513,6 +547,10 @@ .It .Fn rad_add_server .It +.Fn rad_add_server_ex +.It +.Fn rad_add_server_bindto +.It .Fn rad_config .It .Fn rad_create_request @@ -606,9 +644,10 @@ .Fx project by Juniper Networks, Inc. .An Oleg Semyonov -subsequently added the ability to perform RADIUS -accounting. +subsequently added the ability to perform RADIUS accounting. Later additions and changes by .An Michael Bretterklieber . Server mode support was added by .An Alexander Motin . +IPv6 support was implemented by +.An Pawel Jakub Dawidek . Index: radlib.h =================================================================== --- radlib.h +++ radlib.h @@ -190,6 +190,7 @@ #define RAD_ERROR_CAUSE 101 /* Integer */ struct rad_handle; +struct sockaddr; struct timeval; __BEGIN_DECLS @@ -199,8 +200,13 @@ int rad_add_server_ex(struct rad_handle *, const char *, int, const char *, int, int, int, struct in_addr *); +int rad_add_server_bindto(struct rad_handle *, const char *, + int, const char *, int, int, int, + const struct sockaddr *); struct rad_handle *rad_auth_open(void); void rad_bind_to(struct rad_handle *, in_addr_t); +void rad_set_bindto(struct rad_handle *h, + const struct sockaddr *addr); void rad_close(struct rad_handle *); int rad_config(struct rad_handle *, const char *); int rad_continue_send_request(struct rad_handle *, int, Index: radlib.c =================================================================== --- radlib.c +++ radlib.c @@ -50,9 +50,11 @@ /* We need the MPPE_KEY_LEN define */ #include +#include #include #include #include +#include #include #include #include @@ -68,7 +70,7 @@ static void insert_request_authenticator(struct rad_handle *, int); static void insert_message_authenticator(struct rad_handle *, int); static int is_valid_response(struct rad_handle *, int, - const struct sockaddr_in *); + const struct sockaddr *); static int put_password_attr(struct rad_handle *, int, const void *, size_t); static int put_raw_attr(struct rad_handle *, int, @@ -182,8 +184,7 @@ * specified server. */ static int -is_valid_response(struct rad_handle *h, int srv, - const struct sockaddr_in *from) +is_valid_response(struct rad_handle *h, int srv, const struct sockaddr *from) { MD5_CTX ctx; unsigned char md5[MD5_DIGEST_LENGTH]; @@ -199,9 +200,8 @@ srvp = &h->servers[srv]; /* Check the source address */ - if (from->sin_family != srvp->addr.sin_family || - from->sin_addr.s_addr != srvp->addr.sin_addr.s_addr || - from->sin_port != srvp->addr.sin_port) + if (from->sa_family != srvp->addr.ss_family || + memcmp(from, &srvp->addr, from->sa_len) != 0) return 0; /* Check the message length */ @@ -383,11 +383,9 @@ 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); + return rad_add_server_bindto(h, host, port, secret, timeout, tries, + DEAD_TIME, NULL); } int @@ -395,53 +393,134 @@ const char *secret, int timeout, int tries, int dead_time, struct in_addr *bindto) { + struct sockaddr_in sin; + struct sockaddr *sa; + + if (bindto == NULL) { + sa = NULL; + } else { + memset(&sin, 0, sizeof sin); + sin.sin_len = sizeof sin; + sin.sin_family = AF_INET; + sin.sin_port = 0; + memcpy(&sin.sin_addr, bindto, sizeof(sin.sin_addr)); + sa = (struct sockaddr *)&sin; + } + + return rad_add_server_bindto(h, host, port, secret, timeout, tries, + dead_time, sa); +} + +static void +server_bindto(struct rad_server *srvp, sa_family_t family, + const struct sockaddr *bindto) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + + assert(family == AF_INET || family == AF_INET6); + + if (bindto == NULL) { + if (family == AF_INET) { + memset(&sin, 0, sizeof sin); + sin.sin_len = sizeof sin; + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + bindto = (struct sockaddr *)&sin; + } else /* if (family == AF_INET6) */ { + memset(&sin6, 0, sizeof sin6); + sin6.sin6_len = sizeof sin6; + sin6.sin6_family = AF_INET6; + sin6.sin6_port = 0; + sin6.sin6_addr = in6addr_any; + bindto = (struct sockaddr *)&sin6; + } + } + + assert(family == bindto->sa_family); + + memset(&srvp->bindto, 0, sizeof(srvp->bindto)); + memcpy(&srvp->bindto, bindto, bindto->sa_len); +} + +int +rad_add_server_bindto(struct rad_handle *h, const char *host, int port, + const char *secret, int timeout, int tries, int dead_time, + const struct sockaddr *bindto) +{ struct rad_server *srvp; + struct addrinfo hints, *res, *res0; + char servname[NI_MAXSERV]; + int error; if (h->num_servers >= MAXSERVERS) { generr(h, "Too many RADIUS servers specified"); return -1; } - srvp = &h->servers[h->num_servers]; - memset(&srvp->addr, 0, sizeof srvp->addr); - srvp->addr.sin_len = sizeof srvp->addr; - srvp->addr.sin_family = AF_INET; - if (!inet_aton(host, &srvp->addr.sin_addr)) { - struct hostent *hent; + memset(&hints, 0, sizeof(hints)); + if (bindto == NULL) { + hints.ai_family = PF_UNSPEC; + } else { + hints.ai_family = bindto->sa_family; + } + hints.ai_socktype = SOCK_DGRAM; + if (port != 0) { + snprintf(servname, sizeof(servname), "%d", port); + } else if (h->type == RADIUS_AUTH) { + snprintf(servname, sizeof(servname), "radius"); + } else { + snprintf(servname, sizeof(servname), "radacct"); + } + error = getaddrinfo(host, servname, &hints, &res0); + if (error != 0) { + if (strchr(host, ':') != NULL) { + generr(h, "[%s]:%d: %s", host, port, + gai_strerror(error)); + } else { + generr(h, "%s:%d: %s", host, port, gai_strerror(error)); + } + return -1; + } - if ((hent = gethostbyname(host)) == NULL) { - generr(h, "%s: host not found", host); + error = ENOENT; + srvp = &h->servers[h->num_servers]; + for (res = res0; res != NULL; res = res->ai_next) { + if ((srvp->secret = strdup(secret)) == NULL) { + generr(h, "Out of memory"); return -1; } - memcpy(&srvp->addr.sin_addr, hent->h_addr, - sizeof srvp->addr.sin_addr); + + memcpy(&srvp->addr, res->ai_addr, res->ai_addrlen); + srvp->timeout = timeout; + srvp->max_tries = tries; + srvp->num_tries = 0; + srvp->is_dead = 0; + srvp->dead_time = dead_time; + srvp->next_probe = 0; + server_bindto(srvp, res->ai_family, bindto); + if (h->num_servers == 0) { + h->srv = 0; + memcpy(&h->bindto, &srvp->bindto, sizeof(h->bindto)); + } + h->num_servers++; + + error = 0; + + if (h->num_servers >= MAXSERVERS) { + break; + } + srvp = &h->servers[h->num_servers]; } - if (port != 0) - srvp->addr.sin_port = htons((u_short)port); - else { - struct servent *sent; - if (h->type == RADIUS_AUTH) - srvp->addr.sin_port = - (sent = getservbyname("radius", "udp")) != NULL ? - sent->s_port : htons(RADIUS_PORT); - else - srvp->addr.sin_port = - (sent = getservbyname("radacct", "udp")) != NULL ? - sent->s_port : htons(RADACCT_PORT); - } - if ((srvp->secret = strdup(secret)) == NULL) { - generr(h, "Out of memory"); + freeaddrinfo(res0); + + if (error != 0) { + generr(h, "No address found"); return -1; } - 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; } @@ -464,10 +543,69 @@ void rad_bind_to(struct rad_handle *h, in_addr_t addr) { + struct sockaddr_in sin; - h->bindto = addr; + memset(&sin, 0, sizeof sin); + sin.sin_len = sizeof sin; + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = addr; + rad_set_bindto(h, (struct sockaddr *)&sin); } +void +rad_set_bindto(struct rad_handle *h, const struct sockaddr *addr) +{ + + memset(&h->bindto, 0, sizeof(h->bindto)); + memcpy(&h->bindto, addr, addr->sa_len); +} + +static struct sockaddr * +string_to_sockaddr(const char *str, struct sockaddr_storage *ss) +{ + union { + struct in_addr addr4; + struct in6_addr addr6; + } addr; + int ret; + + ret = inet_pton(AF_INET, str, &addr); + if (ret == -1) { + return NULL; + } else if (ret == 1) { + struct sockaddr_in *sin; + + memset(ss, 0, sizeof(*ss)); + sin = (struct sockaddr_in *)ss; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = 0; + memcpy(&sin->sin_addr, &addr, sizeof(sin->sin_addr)); + return (struct sockaddr *)ss; + } + assert(ret == 0); + + ret = inet_pton(AF_INET6, str, &addr); + if (ret == -1) { + return NULL; + } else if (ret == 1) { + struct sockaddr_in6 *sin6; + + memset(ss, 0, sizeof(*ss)); + sin6 = (struct sockaddr_in6 *)ss; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = 0; + memcpy(&sin6->sin6_addr, &addr, sizeof(sin6->sin6_addr)); + return (struct sockaddr *)ss; + } + assert(ret == 0); + + errno = EINVAL; + return NULL; +} + int rad_config(struct rad_handle *h, const char *path) { @@ -503,7 +641,8 @@ unsigned long maxtries; unsigned long dead_time; int port; - struct in_addr bindto; + struct sockaddr_storage bindss; + struct sockaddr *bindto; int i; linenum++; @@ -615,20 +754,19 @@ dead_time = DEAD_TIME; if (bindto_str != NULL) { - bindto.s_addr = inet_addr(bindto_str); - if (bindto.s_addr == INADDR_NONE) { + bindto = string_to_sockaddr(bindto_str, &bindss); + if (bindto == NULL) { generr(h, "%s:%d: invalid bindto", path, linenum); retval = -1; break; } } else - bindto.s_addr = INADDR_ANY; + bindto = NULL; - 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); + if (rad_add_server_bindto(h, host, port, secret, timeout, + maxtries, dead_time, bindto) == -1) { + generr(h, "%s:%d: %s", path, linenum, h->errmsg); retval = -1; break; } @@ -653,14 +791,13 @@ { int n, cur_srv; time_t now; - struct sockaddr_in sin; if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); - return (-1); + return -1; } if (selected) { - struct sockaddr_in from; + struct sockaddr_storage from; socklen_t fromlen; fromlen = sizeof from; @@ -670,7 +807,8 @@ generr(h, "recvfrom: %s", strerror(errno)); return -1; } - if (is_valid_response(h, h->srv, &from)) { + if (is_valid_response(h, h->srv, + (const struct sockaddr *)&from)) { h->in_len = h->in[POS_LENGTH] << 8 | h->in[POS_LENGTH+1]; h->in_pos = POS_ATTRS; @@ -708,29 +846,27 @@ if (h->srv == cur_srv) { generr(h, "No valid RADIUS responses received"); - return (-1); + return -1; } } /* Rebind */ - if (h->bindto != h->servers[h->srv].bindto) { - h->bindto = h->servers[h->srv].bindto; + if (memcmp(&h->bindto, &h->servers[h->srv].bindto, + sizeof(h->bindto)) != 0) { + memcpy(&h->bindto, &h->servers[h->srv].bindto, + sizeof(h->bindto)); close(h->fd); - if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { + if ((h->fd = socket(h->bindto.ss_family, 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) { + if (bind(h->fd, (const struct sockaddr *)&h->bindto, + h->bindto.ss_len) == -1) { generr(h, "bind: %s", strerror(errno)); close(h->fd); h->fd = -1; - return (-1); + return -1; } } @@ -750,7 +886,7 @@ /* Send the request */ n = sendto(h->fd, h->out, h->out_len, 0, (const struct sockaddr *)&h->servers[h->srv].addr, - sizeof h->servers[h->srv].addr); + h->servers[h->srv].addr.ss_len); if (n != h->out_len) tv->tv_sec = 1; /* Do not wait full timeout if send failed. */ else @@ -765,7 +901,7 @@ int rad_receive_request(struct rad_handle *h) { - struct sockaddr_in from; + struct sockaddr_storage from; socklen_t fromlen; int n; @@ -782,8 +918,8 @@ return (-1); } for (n = 0; n < h->num_servers; n++) { - if (h->servers[n].addr.sin_addr.s_addr == from.sin_addr.s_addr) { - h->servers[n].addr.sin_port = from.sin_port; + if (memcmp(&h->servers[n].addr, &from, + h->servers[n].addr.ss_len) == 0) { h->srv = n; break; } @@ -791,8 +927,7 @@ if (h->srv == -1) return (-2); if (is_valid_request(h)) { - h->in_len = h->in[POS_LENGTH] << 8 | - h->in[POS_LENGTH+1]; + h->in_len = h->in[POS_LENGTH] << 8 | h->in[POS_LENGTH+1]; h->in_pos = POS_ATTRS; return (h->in[POS_CODE]); } @@ -806,7 +941,7 @@ if (h->type != RADIUS_SERVER) { generr(h, "denied function call"); - return (-1); + return -1; } /* Fill in the length field in the message */ h->out[POS_LENGTH] = h->out_len >> 8; @@ -819,7 +954,7 @@ /* Send the request */ n = sendto(h->fd, h->out, h->out_len, 0, (const struct sockaddr *)&h->servers[h->srv].addr, - sizeof h->servers[h->srv].addr); + h->servers[h->srv].addr.ss_len); if (n != h->out_len) { if (n == -1) generr(h, "sendto: %s", strerror(errno)); @@ -838,11 +973,11 @@ if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); - return (-1); + return -1; } if (h->num_servers == 0) { generr(h, "No RADIUS servers specified"); - return (-1); + return -1; } h->out[POS_CODE] = code; h->out[POS_IDENT] = ++h->ident; @@ -869,7 +1004,7 @@ if (h->type != RADIUS_SERVER) { generr(h, "denied function call"); - return (-1); + return -1; } h->out[POS_CODE] = code; h->out[POS_IDENT] = h->in[POS_IDENT]; @@ -955,25 +1090,20 @@ { int srv; time_t now; - struct sockaddr_in sin; if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); - return (-1); + return -1; } /* Make sure we have a socket to use */ if (h->fd == -1) { - if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { + if ((h->fd = socket(h->bindto.ss_family, 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 = htons(0); - if (bind(h->fd, (const struct sockaddr *)&sin, - sizeof sin) == -1) { + if (bind(h->fd, (const struct sockaddr *)&h->bindto, + h->bindto.ss_len) == -1) { generr(h, "bind: %s", strerror(errno)); close(h->fd); h->fd = -1; @@ -1061,7 +1191,7 @@ h->type = RADIUS_AUTH; h->out_created = 0; h->eap_msg = 0; - h->bindto = INADDR_ANY; + memset(&h->bindto, 0, sizeof(h->bindto)); } return h; } Index: radlib_private.h =================================================================== --- radlib_private.h +++ radlib_private.h @@ -66,7 +66,7 @@ #define POS_ATTRS 20 /* Start of attributes */ struct rad_server { - struct sockaddr_in addr; /* Address of server */ + struct sockaddr_storage addr; /* Address of server */ char *secret; /* Shared secret */ int timeout; /* Timeout in seconds */ int max_tries; /* Number of tries before giving up */ @@ -74,7 +74,7 @@ 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 sockaddr_storage bindto; /* Bind to address */ }; struct rad_handle { @@ -97,7 +97,7 @@ int in_pos; /* Current position scanning attrs */ int srv; /* Server number we did last */ int type; /* Handle type */ - in_addr_t bindto; /* Current bind address */ + struct sockaddr_storage bindto; /* Current bind address */ }; struct vendor_attribute {