diff --git a/lib/libradius/Makefile b/lib/libradius/Makefile new file mode 100644 index 000000000000..acc3fa3e3220 --- /dev/null +++ b/lib/libradius/Makefile @@ -0,0 +1,41 @@ +# Copyright 1998 Juniper Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +LIB= radius +SRCS= radlib.c +CFLAGS+= -Wall +DPADD+= ${LIBMD} +LDADD+= -lmd +SHLIB_MAJOR= 1 +SHLIB_MINOR= 0 +MAN3+= libradius.3 +MAN5+= radius.conf.5 + +beforeinstall: + ${INSTALL} ${COPY} -o ${BINOWN} -g ${BINGRP} -m 444 \ + ${.CURDIR}/radlib.h ${DESTDIR}/usr/include + +.include diff --git a/lib/libradius/libradius.3 b/lib/libradius/libradius.3 new file mode 100644 index 000000000000..25ccdc09d816 --- /dev/null +++ b/lib/libradius/libradius.3 @@ -0,0 +1,317 @@ +.\" Copyright 1998 Juniper Networks, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd July 29, 1998 +.Dt LIBRADIUS 3 +.Os FreeBSD +.Sh NAME +.Nm libradius +.Nd RADIUS client library +.Sh SYNOPSIS +.Fd #include +.Ft int +.Fn rad_add_server "struct rad_handle *h" "const char *host" "int port" "const char *secret" "int timeout" "int max_tries" +.Ft void +.Fn rad_close "struct rad_handle *h" +.Ft int +.Fn rad_config "struct rad_handle *h" "const char *file" +.Ft int +.Fn rad_create_request "struct rad_handle *h" "int code" +.Ft struct in_addr +.Fn rad_cvt_addr "const void *data" +.Ft u_int32_t +.Fn rad_cvt_int "const void *data" +.Ft char * +.Fn rad_cvt_string "const void *data" "size_t len" +.Ft int +.Fn rad_get_attr "struct rad_handle *h" "const void **data" "size_t *len" +.Ft struct rad_handle * +.Fn rad_open "void" +.Ft int +.Fn rad_put_addr "struct rad_handle *h" "int type" "struct in_addr addr" +.Ft int +.Fn rad_put_attr "struct rad_handle *h" "int type" "const void *data" "size_t len" +.Ft int +.Fn rad_put_int "struct rad_handle *h" "int type" "u_int32_t value" +.Ft int +.Fn rad_put_string "struct rad_handle *h" "int type" "const char *str" +.Ft int +.Fn rad_send_request "struct rad_handle *h" +.Ft const char * +.Fn rad_strerror "struct rad_handle *h" +.Sh DESCRIPTION +The +.Nm +library implements the client side of the Remote Authentication +Dial In User Service (RADIUS). RADIUS, defined in RFC 2138, allows +clients to perform authentication by means of network requests to +remote authentication servers. +.Sh INITIALIZATION +To use the library, an application must first call +.Fn rad_open +to obtain a +.Va struct rad_handle * , +which provides the context for subsequent operations. +Calls to +.Fn rad_open +always succeed unless insufficient virtual memory is available. If +the necessary memory cannot be allocated, +.Fn rad_open +returns +.Dv NULL . +.Pp +Before issuing any RADIUS requests, the library must be made aware +of the servers it can contact. The easiest way to configure the +library is to call +.Fn rad_config . +.Fn rad_config +causes the library to read a configuration file whose format is +described in +.Xr radius.conf 5 . +The pathname of the configuration file is passed as the +.Va file +argument to +.Fn rad_config . +This argument may also be given as +.Dv NULL , +in which case the standard configuration file +.Pa /etc/radius.conf +is used. +.Fn rad_config +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 . +The +.Va host +parameter specifies the server host, either as a fully qualified +domain name or as a dotted-quad IP address in text form. +The +.Va port +parameter specifies the UDP port to contact on the server. If +.Va port +is given as 0, the library looks up the +.Ql radius/udp +service in the network services database, and uses the port found +there. If no entry is found, the library uses port 1812, the standard +RADIUS port. The shared secret for the server host is passed to the +.Va secret +parameter. +It may be any NUL-terminated string of bytes. The RADIUS protocol +ignores all but the leading 128 bytes of the shared secret. +The timeout for receiving replies from the server is passed to the +.Va timeout +parameter, in units of seconds. The maximum number of repeated +requests to make before giving up is passed into the +.Va max_tries +parameter. +.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 +.Fn rad_config . +At most 10 servers may be specified. +When multiple servers are given, they are tried in round-robin +fashion until a valid response is received, or until each server's +.Va max_tries +limit has been reached. +.Sh 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 +.Fn rad_create_request . +In addition to the usual +.Va struct rad_handle * , +this function takes a +.Va code +parameter which specifies the type of the request. Most often this +will be +.Dv RAD_ACCESS_REQUEST . +.Fn rad_create_request +returns 0 on success, or -1 on if an error occurs. +.Pp +After the request has been created with +.Fn rad_create request , +attributes can be attached to it. This is done through calls to +.Fn rad_put_addr , +.Fn rad_put_int , +and +.Fn rad_put_string . +Each accepts a +.Va type +parameter identifying the attribute, and a value which may be +an Internet address, an integer, or a NUL-terminated string, +respectively. +.Pp +The library also provides a function +.Fn rad_put_attr +which can be used to supply a raw, uninterpreted attribute. The +.Va data +argument points to an array of bytes, and the +.Va len +argument specifies its length. +.Pp +The +.Fn rad_put_X +functions return 0 on success, or -1 if an error occurs. +.Sh SENDING THE REQUEST AND RECEIVING THE RESPONSE +After the RADIUS request has been constructed, it is sent by means +of +.Fn rad_send_request . +This function sends the request and waits for a valid reply, +retrying the defined servers in round-robin fashion as necessary. +If a valid response is received, +.Fn rad_send_request +returns the RADIUS code which specifies the type of the response. +This will typically be +.Dv RAD_ACCESS_ACCEPT , +.Dv RAD_ACCESS_REJECT , +or +.Dv RAD_ACCESS_CHALLENGE . +If no valid response is received, +.Fn rad_send_request +returns -1. +.Pp +Like RADIUS requests, each response may contain zero or more +attributes. After a response has been received successfully by +.Fn rad_send_request , +its attributes can be extracted one by one using +.Fn rad_get_attr . +Each time +.Fn rad_get_attr +is called, it gets the next attribute from the current response, and +stores a pointer to the data and the length of the data via the +reference parameters +.Va data +and +.Va len , +respectively. Note that the data resides in the response itself, +and must not be modified. +A successful call to +.Fn rad_get_attr +returns the RADIUS attribute type. +If no more attributes remain in the current response, +.Fn rad_get_attr +returns 0. +If an error such as a malformed attribute is detected, -1 is +returned. +.Pp +The common types of attributes can be decoded using +.Fn rad_cvt_addr , +.Fn rad_cvt_int , +and +.Fn rad_cvt_string . +These functions accept a pointer to the attribute data, which should +have been obtained using +.Fn rad_get_attr . +In the case of +.Fn rad_cvt_string , +the length +.Va len +must also be given. These functions interpret the attribute as an +Internet address, an integer, or a string, respectively, and return +its value. +.Fn rad_cvt_string +returns its value as a NUL-terminated string in dynamically +allocated memory. The application should free the string using +.Xr free 3 +when it is no longer needed. +.Pp +If insufficient virtual memory is available, +.Fn rad_cvt_string +returns +.Dv NULL . +.Fn rad_cvt_addr +and +.Fn rad_cvt_int +cannot fail. +.Sh OBTAINING ERROR MESSAGES +Those functions which accept a +.Va struct rad_handle * +argument record an error message if they fail. The error message +can be retrieved by calling +.Fn rad_strerror . +The message text is overwritten on each new error for the given +.Va struct rad_handle * . +Thus the message must be copied if it is to be preserved through +subsequent library calls using the same handle. +.Sh CLEANUP +To free the resources used by the RADIUS library, call +.Fn rad_close . +.Sh RETURN VALUES +The following functions return a non-negative value on success. If +they detect an error, they return -1 and record an error message +which can be retrieved using +.Fn rad_strerror . +.Pp +.Bl -item -offset indent -compact +.It +.Fn rad_add_server +.It +.Fn rad_config +.It +.Fn rad_create_request +.It +.Fn rad_get_attr +.It +.Fn rad_put_addr +.It +.Fn rad_put_attr +.It +.Fn rad_put_int +.It +.Fn rad_put_string +.It +.Fn rad_send_request +.El +.Pp +The following functions return a +.No non- Ns Dv NULL +pointer on success. If they are unable to allocate sufficient +virtual memory, they return +.Dv NULL , +without recording an error message. +.Pp +.Bl -item -offset indent -compact +.It +.Fn rad_cvt_string +.It +.Fn rad_open +.El +.Sh FILES +.Pa /etc/radius.conf +.Sh SEE ALSO +.Xr radius.conf 5 +.Rs +.%A C. Rigney, et al +.%T Remote Authentication Dial In User Service (RADIUS) +.%O RFC 2138 +.Re +.Sh AUTHORS +This software was written by +.An John Polstra , +and donated to the FreeBSD project by Juniper Networks, Inc. diff --git a/lib/libradius/radius.conf.5 b/lib/libradius/radius.conf.5 new file mode 100644 index 000000000000..2cd72c2952ba --- /dev/null +++ b/lib/libradius/radius.conf.5 @@ -0,0 +1,123 @@ +.\" Copyright 1998 Juniper Networks, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd July 29, 1998 +.Dt RADIUS.CONF 5 +.Os FreeBSD +.Sh NAME +.Nm radius.conf +.Nd RADIUS client configuration file +.Sh SYNOPSIS +.Pa /etc/radius.conf +.Sh DESCRIPTION +.Nm +contains the information necessary to configure the RADIUS client +library. It is parsed by +.Xr rad_config 3 . +The file contains one or more lines of text, each describing a +single RADIUS server which will be used by the library. Leading +white space is ignored, as are empty lines and lines containing +only comments. +.Pp +A RADIUS server is described by two to four fields on a line. The +fields are separated by white space. The +.Ql # +character at the beginning of a field begins a comment, which extends +to the end of the line. A field may be enclosed in double quotes, +in which case it may contain white space and/or begin with the +.Ql # +character. Within a quoted string, the double quote character can +be represented by +.Ql \e\&" , +and the backslash can be represented by +.Ql \e\e . +No other escape sequences are supported. +.Pp +The first field specifies +the server host, either as a fully qualified domain name or as a +dotted-quad IP address. The host may optionally be followed by a +.Ql \&: +and a numeric port number, without intervening white space. If the +port specification is omitted, it defaults to the +.Ql radius +service in the +.Pa /etc/services +file, or to the standard RADIUS port 1812 if there is no such entry in +.Pa /etc/services . +.Pp +The second field contains the shared secret, which should be known +only to the client and server hosts. It is an arbitrary string of +characters, though it must be enclosed in double quotes if it +contains white space. The shared secret may be +any length, but the RADIUS protocol uses only the first 128 +characters. N.B., some popular RADIUS servers have bugs which +prevent them from working properly with secrets longer than 16 +characters. +.Pp +The third field contains a decimal integer specifying the timeout in +seconds for receiving a valid reply from the server. If this field +is omitted, it defaults to 3 seconds. +.Pp +The fourth field contains a decimal integer specifying the maximum +number of attempts that will be made to authenticate with the server +before giving up. If omitted, it defaults to 3 attempts. Note, +this is the total number of attempts and not the number of retries. +.Pp +Up to 10 RADIUS servers may be specified. The servers are tried in +round-robin fashion, until a valid response is received or the +maximum number of tries has been reached for all servers. +.Pp +The standard location for this file is +.Pa /etc/radius.conf . +But an alternate pathname may be specified in the call to +.Xr rad_config 3 . +Since the file contains sensitive information in the form of the +shared secrets, it should not be readable except by root. +.Sh FILES +.Pa /etc/radius.conf +.Sh EXAMPLES +.Bd -literal +# A simple entry using all the defaults: +radius1.domain.com OurLittleSecret + +# A server still using the obsolete RADIUS port, with increased +# timeout and maximum tries: +auth.domain.com:1645 "I can't see you, but I know you're there" 5 4 + +# A server specified by its IP address: +192.168.27.81 $X*#..38947ax-+= +.Ed +.Sh SEE ALSO +.Xr libradius 3 +.Rs +.%A C. Rigney, et al +.%T Remote Authentication Dial In User Service (RADIUS) +.%O RFC 2138 +.Re +.Sh AUTHORS +This documentation was written by +.An John Polstra , +and donated to the FreeBSD project by Juniper Networks, Inc. diff --git a/lib/libradius/radlib.c b/lib/libradius/radlib.c new file mode 100644 index 000000000000..2825c3dae786 --- /dev/null +++ b/lib/libradius/radlib.c @@ -0,0 +1,737 @@ +/*- + * Copyright 1998 Juniper Networks, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "radlib_private.h" + +static void clear_password(struct rad_handle *); +static void generr(struct rad_handle *, const char *, ...) + __printflike(2, 3); +static void insert_scrambled_password(struct rad_handle *, int); +static int is_valid_response(struct rad_handle *, int, + const struct sockaddr_in *); +static int put_password_attr(struct rad_handle *, int, + const void *, size_t); +static int put_raw_attr(struct rad_handle *, int, + const void *, size_t); +static int split(char *, char *[], int, char *, size_t); + +static void +clear_password(struct rad_handle *h) +{ + if (h->pass_len != 0) { + memset(h->pass, 0, h->pass_len); + h->pass_len = 0; + h->pass_pos = 0; + } +} + +static void +generr(struct rad_handle *h, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsnprintf(h->errmsg, ERRSIZE, format, ap); + va_end(ap); +} + +static void +insert_scrambled_password(struct rad_handle *h, int srv) +{ + MD5_CTX ctx; + unsigned char md5[16]; + const struct rad_server *srvp; + int padded_len; + int pos; + + srvp = &h->servers[srv]; + padded_len = h->pass_len == 0 ? 16 : (h->pass_len+15) & ~0xf; + + memcpy(md5, &h->request[POS_AUTH], LEN_AUTH); + for (pos = 0; pos < padded_len; pos += 16) { + int i; + + /* Calculate the new scrambler */ + MD5Init(&ctx); + MD5Update(&ctx, srvp->secret, strlen(srvp->secret)); + MD5Update(&ctx, md5, 16); + MD5Final(md5, &ctx); + + /* + * Mix in the current chunk of the password, and copy + * the result into the right place in the request. Also + * modify the scrambler in place, since we will use this + * in calculating the scrambler for next time. + */ + for (i = 0; i < 16; i++) + h->request[h->pass_pos + pos + i] = + md5[i] ^= h->pass[pos + i]; + } +} + +/* + * Return true if the current response is valid for a request to the + * specified server. + */ +static int +is_valid_response(struct rad_handle *h, int srv, + const struct sockaddr_in *from) +{ + MD5_CTX ctx; + unsigned char md5[16]; + const struct rad_server *srvp; + int len; + + 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) + return 0; + + /* Check the message length */ + if (h->resp_len < POS_ATTRS) + return 0; + len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1]; + if (len > h->resp_len) + return 0; + + /* Check the response authenticator */ + MD5Init(&ctx); + MD5Update(&ctx, &h->response[POS_CODE], POS_AUTH - POS_CODE); + MD5Update(&ctx, &h->request[POS_AUTH], LEN_AUTH); + MD5Update(&ctx, &h->response[POS_ATTRS], len - POS_ATTRS); + MD5Update(&ctx, srvp->secret, strlen(srvp->secret)); + MD5Final(md5, &ctx); + if (memcmp(&h->response[POS_AUTH], md5, sizeof md5) != 0) + return 0; + + return 1; +} + +static int +put_password_attr(struct rad_handle *h, int type, const void *value, size_t len) +{ + int padded_len; + int pad_len; + + if (h->pass_pos != 0) { + generr(h, "Multiple User-Password attributes specified"); + return -1; + } + if (len > PASSSIZE) + len = PASSSIZE; + padded_len = len == 0 ? 16 : (len+15) & ~0xf; + pad_len = padded_len - len; + + /* + * Put in a place-holder attribute containing all zeros, and + * remember where it is so we can fill it in later. + */ + clear_password(h); + put_raw_attr(h, type, h->pass, padded_len); + h->pass_pos = h->req_len - padded_len; + + /* Save the cleartext password, padded as necessary */ + memcpy(h->pass, value, len); + h->pass_len = len; + memset(h->pass + len, 0, pad_len); + return 0; +} + +static int +put_raw_attr(struct rad_handle *h, int type, const void *value, size_t len) +{ + if (len > 253) { + generr(h, "Attribute too long"); + return -1; + } + if (h->req_len + 2 + len > MSGSIZE) { + generr(h, "Maximum message length exceeded"); + return -1; + } + h->request[h->req_len++] = type; + h->request[h->req_len++] = len + 2; + memcpy(&h->request[h->req_len], value, len); + h->req_len += len; + return 0; +} + +int +rad_add_server(struct rad_handle *h, const char *host, int port, + const char *secret, int timeout, int tries) +{ + struct rad_server *srvp; + + 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; + + if ((hent = gethostbyname(host)) == NULL) { + generr(h, "%s: host not found", host); + return -1; + } + memcpy(&srvp->addr.sin_addr, hent->h_addr, + sizeof srvp->addr.sin_addr); + } + if (port != 0) + srvp->addr.sin_port = htons(port); + else { + struct servent *sent; + + srvp->addr.sin_port = + (sent = getservbyname("radius", "udp")) != NULL ? + sent->s_port : htons(RADIUS_PORT); + } + if ((srvp->secret = strdup(secret)) == NULL) { + generr(h, "Out of memory"); + return -1; + } + srvp->timeout = timeout; + srvp->max_tries = tries; + srvp->num_tries = 0; + h->num_servers++; + return 0; +} + +void +rad_close(struct rad_handle *h) +{ + int srv; + + if (h->fd != -1) + close(h->fd); + for (srv = 0; srv < h->num_servers; srv++) { + memset(h->servers[srv].secret, 0, + strlen(h->servers[srv].secret)); + free(h->servers[srv].secret); + } + clear_password(h); + free(h); +} + +int +rad_config(struct rad_handle *h, const char *path) +{ + FILE *fp; + char buf[MAXCONFLINE]; + int linenum; + int retval; + + if (path == NULL) + path = PATH_RADIUS_CONF; + if ((fp = fopen(path, "r")) == NULL) { + generr(h, "Cannot open \"%s\": %s", path, strerror(errno)); + return -1; + } + retval = 0; + linenum = 0; + while (fgets(buf, sizeof buf, fp) != NULL) { + int len; + char *fields[4]; + int nfields; + char msg[ERRSIZE]; + char *host; + char *port_str; + char *secret; + char *timeout_str; + char *maxtries_str; + char *end; + unsigned long timeout; + unsigned long maxtries; + int port; + + linenum++; + len = strlen(buf); + /* We know len > 0, else fgets would have returned NULL. */ + if (buf[len - 1] != '\n') { + if (len == sizeof buf - 1) + generr(h, "%s:%d: line too long", path, + linenum); + else + generr(h, "%s:%d: missing newline", path, + linenum); + retval = -1; + break; + } + buf[len - 1] = '\0'; + + /* Extract the fields from the line. */ + nfields = split(buf, fields, 4, msg, sizeof msg); + if (nfields == -1) { + generr(h, "%s:%d: %s", path, linenum, msg); + retval = -1; + break; + } + if (nfields == 0) + continue; + if (nfields < 2) { + generr(h, "%s:%d: missing shared secret", path, + linenum); + retval = -1; + break; + } + host = fields[0]; + secret = fields[1]; + timeout_str = fields[2]; + maxtries_str = fields[3]; + + /* Parse and validate the fields. */ + host = strtok(host, ":"); + port_str = strtok(NULL, ":"); + if (port_str != NULL) { + port = strtoul(port_str, &end, 10); + if (*end != '\0') { + generr(h, "%s:%d: invalid port", path, + linenum); + retval = -1; + break; + } + } else + port = 0; + if (timeout_str != NULL) { + timeout = strtoul(timeout_str, &end, 10); + if (*end != '\0') { + generr(h, "%s:%d: invalid timeout", path, + linenum); + retval = -1; + break; + } + } else + timeout = TIMEOUT; + if (maxtries_str != NULL) { + maxtries = strtoul(maxtries_str, &end, 10); + if (*end != '\0') { + generr(h, "%s:%d: invalid maxtries", path, + linenum); + retval = -1; + break; + } + } else + maxtries = MAXTRIES; + + if (rad_add_server(h, host, port, secret, timeout, maxtries) == + -1) { + char msg[ERRSIZE]; + + strcpy(msg, h->errmsg); + generr(h, "%s:%d: %s", path, linenum, msg); + retval = -1; + break; + } + } + /* Clear out the buffer to wipe a possible copy of a shared secret */ + memset(buf, 0, sizeof buf); + fclose(fp); + return retval; +} + +int +rad_create_request(struct rad_handle *h, int code) +{ + int i; + + h->request[POS_CODE] = code; + h->request[POS_IDENT] = ++h->ident; + /* Create a random authenticator */ + for (i = 0; i < LEN_AUTH; i += 2) { + long r; + r = random(); + h->request[POS_AUTH+i] = r; + h->request[POS_AUTH+i+1] = r >> 8; + } + h->req_len = POS_ATTRS; + clear_password(h); + return 0; +} + +struct in_addr +rad_cvt_addr(const void *data) +{ + struct in_addr value; + + memcpy(&value.s_addr, data, sizeof value.s_addr); + return value; +} + +u_int32_t +rad_cvt_int(const void *data) +{ + u_int32_t value; + + memcpy(&value, data, sizeof value); + return ntohl(value); +} + +char * +rad_cvt_string(const void *data, size_t len) +{ + char *s; + + s = malloc(len + 1); + if (s != NULL) { + memcpy(s, data, len); + s[len] = '\0'; + } + return s; +} + +/* + * Returns the attribute type. If none are left, returns 0. On failure, + * returns -1. + */ +int +rad_get_attr(struct rad_handle *h, const void **value, size_t *len) +{ + int type; + + if (h->resp_pos >= h->resp_len) + return 0; + if (h->resp_pos + 2 > h->resp_len) { + generr(h, "Malformed attribute in response"); + return -1; + } + type = h->response[h->resp_pos++]; + *len = h->response[h->resp_pos++] - 2; + if (h->resp_pos + *len > h->resp_len) { + generr(h, "Malformed attribute in response"); + return -1; + } + *value = &h->response[h->resp_pos]; + h->resp_pos += *len; + return type; +} + +/* + * Create and initialize a rad_handle structure, and return it to the + * caller. Can fail only if the necessary memory cannot be allocated. + * In that case, it returns NULL. + */ +struct rad_handle * +rad_open(void) +{ + struct rad_handle *h; + + h = (struct rad_handle *)malloc(sizeof(struct rad_handle)); + if (h != NULL) { + srandomdev(); + h->fd = -1; + h->num_servers = 0; + h->ident = random(); + h->errmsg[0] = '\0'; + memset(h->pass, 0, sizeof h->pass); + h->pass_len = 0; + h->pass_pos = 0; + } + return h; +} + +int +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_attr(struct rad_handle *h, int type, const void *value, size_t len) +{ + return type == RAD_USER_PASSWORD ? + put_password_attr(h, type, value, len) : + put_raw_attr(h, type, value, len); +} + +int +rad_put_int(struct rad_handle *h, int type, u_int32_t value) +{ + u_int32_t nvalue; + + nvalue = htonl(value); + return rad_put_attr(h, type, &nvalue, sizeof nvalue); +} + +int +rad_put_string(struct rad_handle *h, int type, const char *str) +{ + return rad_put_attr(h, type, str, strlen(str)); +} + +/* + * Returns the response type code on success, or -1 on failure. + */ +int +rad_send_request(struct rad_handle *h) +{ + int total_tries; + int try; + int srv; + int n; + int got_valid_response; + + /* 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; + } + memset(&sin, 0, sizeof sin); + sin.sin_len = sizeof sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(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; + } + } + + /* Make sure the user gave us a password */ + if (h->pass_pos == 0) { + generr(h, "No User-Password attribute given"); + return -1; + } + + /* Fill in the length field in the message */ + h->request[POS_LENGTH] = h->req_len >> 8; + h->request[POS_LENGTH+1] = h->req_len; + + /* + * Count the total number of tries we will make, and zero the + * counter for each server. + */ + total_tries = 0; + for (srv = 0; srv < h->num_servers; srv++) { + total_tries += h->servers[srv].max_tries; + h->servers[srv].num_tries = 0; + } + if (total_tries == 0) { + generr(h, "No RADIUS servers specified"); + return -1; + } + + srv = 0; + got_valid_response = 0; + for (try = 0; try < total_tries; try++) { + struct timeval timelimit; + struct timeval tv; + + /* + * 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[srv].num_tries >= + h->servers[srv].max_tries) + if (++srv >= h->num_servers) + srv = 0; + + /* Insert the scrambled password into the request */ + insert_scrambled_password(h, srv); + + /* Send the request */ + n = sendto(h->fd, h->request, h->req_len, 0, + (const struct sockaddr *)&h->servers[srv].addr, + sizeof h->servers[srv].addr); + if (n != h->req_len) { + if (n == -1) + generr(h, "sendto: %s", strerror(errno)); + else + generr(h, "sendto: short write"); + return -1; + } + h->servers[srv].num_tries++; + + /* Wait for a valid response */ + gettimeofday(&timelimit, NULL); + timelimit.tv_sec += h->servers[srv].timeout; + + tv.tv_sec = h->servers[srv].timeout; + tv.tv_usec = 0; + for ( ; ; ) { + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(h->fd, &readfds); + n = select(h->fd + 1, &readfds, NULL, NULL, &tv); + if (n == -1) { + generr(h, "select: %s", strerror(errno)); + return -1; + } + if (n == 0) /* Timed out */ + break; + if (FD_ISSET(h->fd, &readfds)) { + struct sockaddr_in from; + int fromlen; + + fromlen = sizeof from; + h->resp_len = recvfrom(h->fd, h->response, + MSGSIZE, MSG_WAITALL, + (struct sockaddr *)&from, &fromlen); + if (h->resp_len == -1) { + generr(h, "recvfrom: %s", + strerror(errno)); + return -1; + } + if (is_valid_response(h, srv, &from)) { + got_valid_response = 1; + break; + } + } + /* Compute a new timeout */ + gettimeofday(&tv, NULL); + timersub(&timelimit, &tv, &tv); + if (tv.tv_sec < 0) /* Still poll once more */ + timerclear(&tv); + } + if (got_valid_response) + break; + /* Advance to the next server */ + if (++srv >= h->num_servers) + srv = 0; + } + if (!got_valid_response) { + generr(h, "No valid RADIUS responses received"); + return -1; + } + h->resp_len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1]; + h->resp_pos = POS_ATTRS; + return h->response[POS_CODE]; +} + +const char * +rad_strerror(struct rad_handle *h) +{ + return h->errmsg; +} + +/* + * Destructively split a string into fields separated by white space. + * `#' at the beginning of a field begins a comment that extends to the + * end of the string. Fields may be quoted with `"'. Inside quoted + * strings, the backslash escapes `\"' and `\\' are honored. + * + * Pointers to up to the first maxfields fields are stored in the fields + * array. Missing fields get NULL pointers. + * + * The return value is the actual number of fields parsed, and is always + * <= maxfields. + * + * On a syntax error, places a message in the msg string, and returns -1. + */ +static int +split(char *str, char *fields[], int maxfields, char *msg, size_t msglen) +{ + char *p; + int i; + static const char ws[] = " \t"; + + for (i = 0; i < maxfields; i++) + fields[i] = NULL; + p = str; + i = 0; + while (*p != '\0') { + p += strspn(p, ws); + if (*p == '#' || *p == '\0') + break; + if (i >= maxfields) { + snprintf(msg, msglen, "line has too many fields"); + return -1; + } + if (*p == '"') { + char *dst; + + dst = ++p; + fields[i] = dst; + while (*p != '"') { + if (*p == '\\') { + p++; + if (*p != '"' && *p != '\\' && + *p != '\0') { + snprintf(msg, msglen, + "invalid `\\' escape"); + return -1; + } + } + if (*p == '\0') { + snprintf(msg, msglen, + "unterminated quoted string"); + return -1; + } + *dst++ = *p++; + } + *dst = '\0'; + p++; + if (*fields[i] == '\0') { + snprintf(msg, msglen, + "empty quoted string not permitted"); + return -1; + } + if (*p != '\0' && strspn(p, ws) == 0) { + snprintf(msg, msglen, "quoted string not" + " followed by white space"); + return -1; + } + } else { + fields[i] = p; + p += strcspn(p, ws); + if (*p != '\0') + *p++ = '\0'; + } + i++; + } + return i; +} diff --git a/lib/libradius/radlib.h b/lib/libradius/radlib.h new file mode 100644 index 000000000000..146bcd51b29a --- /dev/null +++ b/lib/libradius/radlib.h @@ -0,0 +1,125 @@ +/*- + * Copyright 1998 Juniper Networks, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _RADLIB_H_ +#define _RADLIB_H_ + +#include +#include + +/* Message types */ +#define RAD_ACCESS_REQUEST 1 +#define RAD_ACCESS_ACCEPT 2 +#define RAD_ACCESS_REJECT 3 +#define RAD_ACCESS_CHALLENGE 11 + +/* Attribute types and values */ +#define RAD_USER_NAME 1 /* String */ +#define RAD_USER_PASSWORD 2 /* String */ +#define RAD_CHAP_PASSWORD 3 /* String */ +#define RAD_NAS_IP_ADDRESS 4 /* IP address */ +#define RAD_NAS_PORT 5 /* Integer */ +#define RAD_SERVICE_TYPE 6 /* Integer */ + #define RAD_LOGIN 1 + #define RAD_FRAMED 2 + #define RAD_CALLBACK_LOGIN 3 + #define RAD_CALLBACK_FRAMED 4 + #define RAD_OUTBOUND 5 + #define RAD_ADMINISTRATIVE 6 + #define RAD_NAS_PROMPT 7 + #define RAD_AUTHENTICATE_ONLY 8 + #define RAD_CALLBACK_NAS_PROMPT 9 +#define RAD_FRAMED_PROTOCOL 7 /* Integer */ + #define RAD_PPP 1 + #define RAD_SLIP 2 + #define RAD_ARAP 3 /* Appletalk */ + #define RAD_GANDALF 4 + #define RAD_XYLOGICS 5 +#define RAD_FRAMED_IP_ADDRESS 8 /* IP address */ +#define RAD_FRAMED_IP_NETMASK 9 /* IP address */ +#define RAD_FRAMED_ROUTING 10 /* Integer */ +#define RAD_FILTER_ID 11 /* String */ +#define RAD_FRAMED_MTU 12 /* Integer */ +#define RAD_FRAMED_COMPRESSION 13 /* Integer */ +#define RAD_LOGIN_IP_HOST 14 /* IP address */ +#define RAD_LOGIN_SERVICE 15 /* Integer */ +#define RAD_LOGIN_TCP_PORT 16 /* Integer */ + /* unassiged 17 */ +#define RAD_REPLY_MESSAGE 18 /* String */ +#define RAD_CALLBACK_NUMBER 19 /* String */ +#define RAD_CALLBACK_ID 20 /* String */ + /* unassiged 21 */ +#define RAD_FRAMED_ROUTE 22 /* String */ +#define RAD_FRAMED_IPX_NETWORK 23 /* IP address */ +#define RAD_STATE 24 /* String */ +#define RAD_CLASS 25 /* Integer */ +#define RAD_VENDOR_SPECIFIC 26 /* Integer */ +#define RAD_SESSION_TIMEOUT 27 /* Integer */ +#define RAD_IDLE_TIMEOUT 28 /* Integer */ +#define RAD_TERMINATION_ACTION 29 /* Integer */ +#define RAD_CALLED_STATION_ID 30 /* String */ +#define RAD_CALLING_STATION_ID 31 /* String */ +#define RAD_NAS_IDENTIFIER 32 /* Integer */ +#define RAD_PROXY_STATE 33 /* Integer */ +#define RAD_LOGIN_LAT_SERVICE 34 /* Integer */ +#define RAD_LOGIN_LAT_NODE 35 /* Integer */ +#define RAD_LOGIN_LAT_GROUP 36 /* Integer */ +#define RAD_FRAMED_APPLETALK_LINK 37 /* Integer */ +#define RAD_FRAMED_APPLETALK_NETWORK 38 /* Integer */ +#define RAD_FRAMED_APPLETALK_ZONE 39 /* Integer */ + /* reserved for accounting 40-59 */ +#define RAD_CHAP_CHALLENGE 60 /* String */ +#define RAD_NAS_PORT_TYPE 61 /* Integer */ +#define RAD_PORT_LIMIT 62 /* Integer */ +#define RAD_LOGIN_LAT_PORT 63 /* Integer */ + +struct rad_handle; + +__BEGIN_DECLS +int rad_add_server(struct rad_handle *, + const char *, int, const char *, int, int); +void rad_close(struct rad_handle *); +int rad_config(struct rad_handle *, const char *); +int rad_create_request(struct rad_handle *, int); +struct in_addr rad_cvt_addr(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 **, + size_t *); +struct rad_handle *rad_open(void); +int rad_put_addr(struct rad_handle *, int, struct in_addr); +int rad_put_attr(struct rad_handle *, int, + const void *, size_t); +int rad_put_int(struct rad_handle *, int, u_int32_t); +int rad_put_string(struct rad_handle *, int, + const char *); +int rad_send_request(struct rad_handle *); +const char *rad_strerror(struct rad_handle *); +__END_DECLS + +#endif /* _RADLIB_H_ */ diff --git a/lib/libradius/radlib_private.h b/lib/libradius/radlib_private.h new file mode 100644 index 000000000000..d3301e326192 --- /dev/null +++ b/lib/libradius/radlib_private.h @@ -0,0 +1,82 @@ +/*- + * Copyright 1998 Juniper Networks, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RADLIB_PRIVATE_H +#define RADLIB_PRIVATE_H + +#include +#include + +#include "radlib.h" + +/* Defaults */ +#define MAXTRIES 3 +#define PATH_RADIUS_CONF "/etc/radius.conf" +#define RADIUS_PORT 1812 +#define TIMEOUT 3 /* In seconds */ + +/* Limits */ +#define ERRSIZE 128 /* Maximum error message length */ +#define MAXCONFLINE 1024 /* Maximum config file line length */ +#define MAXSERVERS 10 /* Maximum number of servers to try */ +#define MSGSIZE 4096 /* Maximum RADIUS message */ +#define PASSSIZE 128 /* Maximum significant password chars */ + +/* Positions of fields in RADIUS messages */ +#define POS_CODE 0 /* Message code */ +#define POS_IDENT 1 /* Identifier */ +#define POS_LENGTH 2 /* Message length */ +#define POS_AUTH 4 /* Authenticator */ +#define LEN_AUTH 16 /* Length of authenticator */ +#define POS_ATTRS 20 /* Start of attributes */ + +struct rad_server { + struct sockaddr_in addr; /* Address of server */ + char *secret; /* Shared secret */ + int timeout; /* Timeout in seconds */ + int max_tries; /* Number of tries before giving up */ + int num_tries; /* Number of tries so far */ +}; + +struct rad_handle { + int fd; /* Socket file descriptor */ + struct rad_server servers[MAXSERVERS]; /* Servers to contact */ + int num_servers; /* Number of valid server entries */ + int ident; /* Current identifier value */ + char errmsg[ERRSIZE]; /* Most recent error message */ + unsigned char request[MSGSIZE]; /* Request to send */ + int req_len; /* Length of request */ + char pass[PASSSIZE]; /* Cleartext password */ + int pass_len; /* Length of cleartext password */ + int pass_pos; /* Position of scrambled password */ + unsigned char response[MSGSIZE]; /* Response received */ + int resp_len; /* Length of response */ + int resp_pos; /* Current position scanning attrs */ +}; + +#endif