diff options
Diffstat (limited to 'src')
120 files changed, 11135 insertions, 838 deletions
diff --git a/src/include/clients.h b/src/include/clients.h index 46b5b3b..7e962b6 100644 --- a/src/include/clients.h +++ b/src/include/clients.h @@ -43,7 +43,11 @@ typedef struct radclient { char const *secret; //!< Secret PSK. - bool message_authenticator; //!< Require RADIUS message authenticator in requests. + fr_bool_auto_t require_ma; //!< Require RADIUS message authenticator in requests. + + bool dynamic_require_ma; //!< for dynamic clients + + fr_bool_auto_t limit_proxy_state; //!< Limit Proxy-State in requests char const *nas_type; //!< Type of client (arbitrary). diff --git a/src/include/conffile.h b/src/include/conffile.h index b996881..237469c 100644 --- a/src/include/conffile.h +++ b/src/include/conffile.h @@ -140,6 +140,7 @@ typedef struct timeval _timeval_t; #define PW_TYPE_MULTI (1 << 18) //!< CONF_PAIR can have multiple copies. #define PW_TYPE_NOT_EMPTY (1 << 19) //!< CONF_PAIR is required to have a non zero length value. #define PW_TYPE_FILE_EXISTS ((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist +#define PW_TYPE_IGNORE_DEFAULT (1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing /* @} **/ #define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\ diff --git a/src/include/dlist.h b/src/include/dlist.h new file mode 100644 index 0000000..c1bc1d5 --- /dev/null +++ b/src/include/dlist.h @@ -0,0 +1,63 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * + * @file dlist.h + * @brief doubly linked lists + * + * @copyright 2023 Network RADIUS SAS (legal@networkradius.com) + */ + +#ifndef RADIUS_DLIST_H +#define RADIUS_DLIST_H + +RCSIDH(dlist_h, "$Id$") + +/* + * We have an internal cache, keyed by (mac + ssid). + * + * It returns the PMK and PSK for the user. + */ +typedef struct fr_dlist_s fr_dlist_t; + +struct fr_dlist_s { + fr_dlist_t *prev; + fr_dlist_t *next; +}; + +static inline void fr_dlist_entry_init(fr_dlist_t *entry) +{ + entry->prev = entry->next = entry; +} + +static inline CC_HINT(nonnull) void fr_dlist_entry_unlink(fr_dlist_t *entry) +{ + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + entry->prev = entry->next = entry; +} + +static inline CC_HINT(nonnull) void fr_dlist_insert_tail(fr_dlist_t *head, fr_dlist_t *entry) +{ + entry->next = head; + entry->prev = head->prev; + head->prev->next = entry; + head->prev = entry; +} + +#endif /* RADIUS_DLIST_H */ diff --git a/src/include/event.h b/src/include/event.h index 0409728..822da96 100644 --- a/src/include/event.h +++ b/src/include/event.h @@ -39,6 +39,8 @@ typedef void (*fr_event_fd_handler_t)(fr_event_list_t *el, int sock, void *ctx); fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status); +extern int fr_ev_max_fds; /* must be a power of 2 */ + int fr_event_list_num_fds(fr_event_list_t *el); int fr_event_list_num_elements(fr_event_list_t *el); diff --git a/src/include/features-h b/src/include/features-h index 158541f..1e2f29e 100644 --- a/src/include/features-h +++ b/src/include/features-h @@ -69,7 +69,7 @@ #ifdef WITH_TLS # ifdef WITH_COA # ifndef WITHOUT_COA_TUNNEL -# define WITH_COA_TUNNEL (1) +//# define WITH_COA_TUNNEL (1) # endif # endif #endif diff --git a/src/include/libradius.h b/src/include/libradius.h index 777927e..5cb5b06 100644 --- a/src/include/libradius.h +++ b/src/include/libradius.h @@ -410,6 +410,11 @@ typedef struct radius_packet { #ifdef WITH_RADIUSV11 bool radiusv11; #endif + bool tls; //!< uses secure transport + + bool message_authenticator; + bool proxy_state; + bool eap_message; } RADIUS_PACKET; typedef enum { @@ -527,6 +532,13 @@ DICT_VENDOR *dict_vendorbyvalue(int vendor); /* radius.c */ int rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret); bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason); + +/* + * 1 == require_ma + * 2 == msg_peek + * 4 == limit_proxy_state + * 8 == require_ma for Access-* replies and Protocol-Error + */ RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags); ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code); void rad_recv_discard(int sockfd); @@ -720,7 +732,7 @@ extern bool fr_dns_lookups; /* do IP -> hostname lookups? */ extern bool fr_hostname_lookups; /* do hostname -> IP lookups? */ extern int fr_debug_lvl; /* 0 = no debugging information */ extern uint32_t fr_max_attributes; /* per incoming packet */ -#define FR_MAX_PACKET_CODE (52) +#define FR_MAX_PACKET_CODE (53) extern char const *fr_packet_codes[FR_MAX_PACKET_CODE]; #define is_radius_code(_x) ((_x > 0) && (_x < FR_MAX_PACKET_CODE)) extern FILE *fr_log_fp; @@ -958,6 +970,12 @@ int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout); } #endif +typedef enum { + FR_BOOL_FALSE = 0, + FR_BOOL_TRUE, + FR_BOOL_AUTO, +} fr_bool_auto_t; + #include <freeradius-devel/packet.h> #ifdef WITH_TCP diff --git a/src/include/radius.h b/src/include/radius.h index 473528d..147d674 100644 --- a/src/include/radius.h +++ b/src/include/radius.h @@ -61,6 +61,7 @@ typedef enum { PW_CODE_COA_REQUEST = 43, //!< RFC3575/RFC5176 - CoA-Request PW_CODE_COA_ACK = 44, //!< RFC3575/RFC5176 - CoA-Ack (positive) PW_CODE_COA_NAK = 45, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform) + PW_CODE_PROTOCOL_ERROR = 52, //!< RFC7930 - Protocol layer issue PW_CODE_MAX = 255, //!< Maximum possible code } PW_CODE; diff --git a/src/include/radiusd.h b/src/include/radiusd.h index 594a6bd..c7c03cd 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -141,6 +141,8 @@ typedef struct main_config { uint32_t cleanup_delay; //!< How long before cleaning up cached responses. uint32_t max_requests; + uint32_t proxy_dedup_window; //!< suppress duplicate retransmitssions from a NAS + bool postauth_client_lost; //!< Whether to run Post-Auth-Type Client-Lost section uint32_t debug_level; @@ -174,6 +176,9 @@ typedef struct main_config { bool exiting; //!< are we exiting? + fr_bool_auto_t require_ma; //!< global configuration for all clients and home servers + + fr_bool_auto_t limit_proxy_state; //!< global configuration for all clients #ifdef ENABLE_OPENSSL_VERSION_CHECK char const *allow_vulnerable_openssl; //!< The CVE number of the last security issue acknowledged. @@ -194,9 +199,8 @@ typedef struct main_config { typedef enum { REQUEST_ACTIVE = 1, REQUEST_STOP_PROCESSING, - REQUEST_COUNTED } rad_master_state_t; -#define REQUEST_MASTER_NUM_STATES (REQUEST_COUNTED + 1) +#define REQUEST_MASTER_NUM_STATES (REQUEST_STOP_PROCESSING + 1) typedef enum { REQUEST_QUEUED = 1, @@ -320,6 +324,7 @@ struct rad_request { #define RAD_REQUEST_OPTION_COA (1 << 0) #define RAD_REQUEST_OPTION_CTX (1 << 1) #define RAD_REQUEST_OPTION_CANCELLED (1 << 2) +#define RAD_REQUEST_OPTION_STATS (1 << 3) #define SECONDS_PER_DAY 86400 #define MAX_REQUEST_TIME 30 @@ -565,6 +570,8 @@ int main_config_free(void); void main_config_hup(void); void hup_logfile(void); +int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str); + /* listen.c */ void listen_free(rad_listen_t **head); int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag); diff --git a/src/include/realms.h b/src/include/realms.h index 23806f4..cc5d4c1 100644 --- a/src/include/realms.h +++ b/src/include/realms.h @@ -58,6 +58,8 @@ typedef struct fr_socket_limit_t { uint32_t num_requests; uint32_t lifetime; uint32_t idle_timeout; + uint32_t read_timeout; + uint32_t write_timeout; } fr_socket_limit_t; typedef struct home_server { @@ -69,6 +71,8 @@ typedef struct home_server { bool dual; //!< One of a pair of homeservers on consecutive ports. bool dynamic; //!< is this a dynamically added home server? bool nonblock; //!< Enable a socket non-blocking to the home server. + fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server + #ifdef WITH_COA_TUNNEL bool recv_coa; //!< receive CoA packets, too #endif diff --git a/src/include/tls-h b/src/include/tls-h index 4bf1665..506fb19 100644 --- a/src/include/tls-h +++ b/src/include/tls-h @@ -152,6 +152,9 @@ typedef struct _tls_session_t { //!< If set to no then only the first fragment contains length. int peap_flag; + VALUE_PAIR *outer_tlvs; //!< only for TEAP, and only for the first fragment. + uint8_t *outer_tlvs_octets; //!< only for TEAP, needed for Crypto-Binding TLV + size_t tls_record_in_total_len; //!< How long the peer indicated the complete tls record //!< would be. size_t tls_record_in_recvd_len; //!< How much of the record we've received so far. @@ -176,17 +179,19 @@ typedef struct _tls_session_t { * * 0 1 2 3 4 5 6 7 8 * +-+-+-+-+-+-+-+-+ - * |L M S R R R R R| + * |L M S O R R R R| * +-+-+-+-+-+-+-+-+ * * L = Length included * M = More fragments * S = EAP-TLS start + * O = outer TLV length included (4 octets, only for TEAP) * R = Reserved */ #define TLS_START(x) (((x) & 0x20) != 0) #define TLS_MORE_FRAGMENTS(x) (((x) & 0x40) != 0) #define TLS_LENGTH_INCLUDED(x) (((x) & 0x80) != 0) +#define TLS_OUTER_TLV_INCLUDED(x) (((x) & 0x10) != 0) #define TLS_CHANGE_CIPHER_SPEC(x) (((x) & 0x0014) == 0x0014) #define TLS_ALERT(x) (((x) & 0x0015) == 0x0015) @@ -195,6 +200,7 @@ typedef struct _tls_session_t { #define SET_START(x) ((x) | (0x20)) #define SET_MORE_FRAGMENTS(x) ((x) | (0x40)) #define SET_LENGTH_INCLUDED(x) ((x) | (0x80)) +#define SET_OUTER_TLV_INCLUDED(x) ((x) | (0x10)) /* * Following enums from rfc2246 @@ -351,6 +357,8 @@ struct fr_tls_server_conf_t { SSL_CTX *ctx; CONF_SECTION *cs; + char const *name; //!< name of the thing doing TLS. + char const *private_key_password; char const *private_key_file; char const *certificate_file; diff --git a/src/lib/event.c b/src/lib/event.c index 9eb9d1a..0d926ad 100644 --- a/src/lib/event.c +++ b/src/lib/event.c @@ -47,7 +47,9 @@ typedef struct fr_event_fd_t { void *ctx; } fr_event_fd_t; -#define FR_EV_MAX_FDS (512) +#define FR_EV_MAX_EVENTS (512) + +int fr_ev_max_fds = FR_EV_MAX_EVENTS; #undef USEC #define USEC (1000000) @@ -71,10 +73,10 @@ struct fr_event_list_t { fd_set write_fds; #else int kq; - struct kevent events[FR_EV_MAX_FDS]; /* so it doesn't go on the stack every time */ + struct kevent events[FR_EV_MAX_EVENTS]; /* so it doesn't go on the stack every time */ #endif - fr_event_fd_t readers[FR_EV_MAX_FDS]; + fr_event_fd_t readers[1]; }; /* @@ -128,11 +130,12 @@ fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) int i; fr_event_list_t *el; - el = talloc_zero(ctx, fr_event_list_t); + el = (fr_event_list_t *) talloc_zero_array(ctx, uint8_t, sizeof(fr_event_list_t) + fr_ev_max_fds * sizeof(el->readers[0])); if (!fr_assert(el)) { return NULL; } talloc_set_destructor(el, _event_list_free); + talloc_set_type(el, fr_event_list_t); el->times = fr_heap_create(fr_event_list_time_cmp, offsetof(fr_event_t, heap)); if (!el->times) { @@ -140,7 +143,7 @@ fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) return NULL; } - for (i = 0; i < FR_EV_MAX_FDS; i++) { + for (i = 0; i < fr_ev_max_fds; i++) { el->readers[i].fd = -1; } @@ -363,7 +366,7 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, return 0; } - if (el->num_readers >= FR_EV_MAX_FDS) { + if (el->num_readers >= fr_ev_max_fds) { fr_strerror_printf("Too many readers"); return 0; } @@ -385,11 +388,11 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, * usually less than 256, we do "FD & 0xff", which is a * good guess, and makes the lookups mostly O(1). */ - for (i = 0; i < FR_EV_MAX_FDS; i++) { + for (i = 0; i < fr_ev_max_fds; i++) { int j; struct kevent evset; - j = (i + fd) & (FR_EV_MAX_FDS - 1); + j = (i + fd) & (fr_ev_max_fds - 1); if (el->readers[j].fd >= 0) continue; @@ -469,11 +472,11 @@ int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, if (type != 0) return 0; #ifdef HAVE_KQUEUE - for (i = 0; i < FR_EV_MAX_FDS; i++) { + for (i = 0; i < fr_ev_max_fds; i++) { int j; struct kevent evset; - j = (i + fd) & (FR_EV_MAX_FDS - 1); + j = (i + fd) & (fr_ev_max_fds - 1); if (el->readers[j].fd != fd) continue; @@ -528,11 +531,11 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) if (type != 0) return 0; #ifdef HAVE_KQUEUE - for (i = 0; i < FR_EV_MAX_FDS; i++) { + for (i = 0; i < fr_ev_max_fds; i++) { int j; struct kevent evset; - j = (i + fd) & (FR_EV_MAX_FDS - 1); + j = (i + fd) & (fr_ev_max_fds - 1); if (el->readers[j].fd != fd) continue; @@ -676,7 +679,7 @@ int fr_event_loop(fr_event_list_t *el) ts_wake = NULL; } - rcode = kevent(el->kq, NULL, 0, el->events, FR_EV_MAX_FDS, ts_wake); + rcode = kevent(el->kq, NULL, 0, el->events, FR_EV_MAX_EVENTS, ts_wake); #endif /* HAVE_KQUEUE */ if (fr_heap_num_elements(el->times) > 0) { diff --git a/src/lib/packet.c b/src/lib/packet.c index 971980b..0f870f5 100644 --- a/src/lib/packet.c +++ b/src/lib/packet.c @@ -723,6 +723,8 @@ bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, */ id = fd = -1; + if (request->id >= 0 && request->id < 256) + id = request->id; start_i = fr_rand() & SOCKOFFSET_MASK; #define ID_i ((i + start_i) & SOCKOFFSET_MASK) @@ -832,6 +834,18 @@ bool fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto, */ /* + * An explicit ID was requested + */ + + if (id != -1) { + if ((ps->id[(id >> 3) & 0x1f] & (1 << (id & 0x07))) != 0) continue; + + ps->id[(id >> 3) & 0x1f] |= (1 << (id & 0x07)); + fd = i; + break; + } + + /* * Look for a free Id, starting from a random number. */ start_j = fr_rand() & 0x1f; @@ -1076,7 +1090,7 @@ void fr_packet_header_print(FILE *fp, RADIUS_PACKET *packet, bool received) * This really belongs in a utility library */ if (is_radius_code(packet->code)) { - fprintf(fp, "%s %s Id %i from %s%s%s:%x to %s%s%s:%u length %zu\n", + fprintf(fp, "%s %s Id %i from %s%s%s:%u to %s%s%s:%u length %zu\n", received ? "Received" : "Sent", fr_packet_codes[packet->code], packet->id, @@ -1094,7 +1108,7 @@ void fr_packet_header_print(FILE *fp, RADIUS_PACKET *packet, bool received) packet->dst_port, packet->data_len); } else { - fprintf(fp, "%s code %u Id %i from %s%s%s:%u to %s%s%s:%i length %zu\n", + fprintf(fp, "%s code %u Id %i from %s%s%s:%u to %s%s%s:%u length %zu\n", received ? "Received" : "Sent", packet->code, packet->id, diff --git a/src/lib/print.c b/src/lib/print.c index 57455b6..83aa267 100644 --- a/src/lib/print.c +++ b/src/lib/print.c @@ -529,6 +529,7 @@ size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool switch (vp->da->type) { case PW_TYPE_STRING: + case PW_TYPE_OCTETS: for (q = vp->vp_strvalue; q < vp->vp_strvalue + vp->vp_length; q++) { /* Indicate truncation */ if (freespace < 3) return outlen + 1; diff --git a/src/lib/radius.c b/src/lib/radius.c index b2de15b..a19c9a4 100644 --- a/src/lib/radius.c +++ b/src/lib/radius.c @@ -145,8 +145,9 @@ char const *fr_packet_codes[FR_MAX_PACKET_CODE] = { "47", "48", "49", - "IP-Address-Allocate", - "IP-Address-Release", //!< 50 + "IP-Address-Allocate", //!< 50 + "IP-Address-Release", + "Protocol-Error", }; @@ -536,7 +537,7 @@ static void make_secret(uint8_t *digest, uint8_t const *vector, fr_md5_destroy(&context); } -#define MAX_PASS_LEN (128) +#define MAX_PASS_LEN (256) static void make_passwd(uint8_t *output, ssize_t *outlen, uint8_t const *input, size_t inlen, char const *secret, uint8_t const *vector) @@ -1819,6 +1820,14 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original, return rad_vp2vsa(packet, original, secret, pvp, start, room); } +static const bool code2ma[FR_MAX_PACKET_CODE] = { + [ PW_CODE_ACCESS_REQUEST ] = true, + [ PW_CODE_ACCESS_ACCEPT ] = true, + [ PW_CODE_ACCESS_REJECT ] = true, + [ PW_CODE_ACCESS_CHALLENGE ] = true, + [ PW_CODE_STATUS_SERVER ] = true, + [ PW_CODE_PROTOCOL_ERROR ] = true, +}; /** Encode a packet * @@ -1831,6 +1840,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, uint16_t total_length; int len; VALUE_PAIR const *reply; + bool seen_ma = false; /* * A 4K packet, aligned on 64-bits. @@ -1883,6 +1893,12 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, id = htonl(id); memcpy(hdr->vector, &id, sizeof(id)); memset(hdr->vector + sizeof(id), 0, sizeof(hdr->vector) - sizeof(id)); + + /* + * We don't encode Message-Authenticator + */ + seen_ma = true; + packet->offset = -1; } else #endif { @@ -1909,6 +1925,27 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, */ /* + * Always add Message-Authenticator for replies to + * Access-Request packets, and for all Access-Accept, + * Access-Reject, Access-Challenge. + * + * It must be the FIRST attribute in the packet. + */ + if (!packet->tls && + ((code2ma[packet->code]) || (original && code2ma[original->code]))) { + seen_ma = true; + + packet->offset = RADIUS_HDR_LEN; + + ptr[0] = PW_MESSAGE_AUTHENTICATOR; + ptr[1] = 18; + memset(ptr + 2, 0, 16); + + ptr += 18; + total_length += 18; + } + + /* * Loop over the reply attributes for the packet. */ reply = packet->vps; @@ -1943,18 +1980,9 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, #ifdef WITH_RADIUSV11 /* - * Do not encode Message-Authenticator for RADIUS/1.1 - */ - if ((reply->da->vendor == 0) && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { - reply = reply->next; - continue; - } - - - /* * Do not encode Original-Packet-Code for RADIUS/1.1 */ - if (reply->da->vendor == ((unsigned int) PW_EXTENDED_ATTRIBUTE_1 << 24) && (reply->da->attr == 4)) { + if (packet->radiusv11 && reply->da->vendor == ((unsigned int) PW_EXTENDED_ATTRIBUTE_1 << 24) && (reply->da->attr == 4)) { reply = reply->next; continue; } @@ -1984,15 +2012,13 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, * length and initial value. */ if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { -#ifdef WITH_RADIUSV11 /* - * RADIUSV11 does not encode or verify Message-Authenticator. + * We have already encoded the Message-Authenticator, don't do it again. */ - if (packet->radiusv11) { + if (seen_ma) { reply = reply->next; continue; } -#endif if (room < 18) break; @@ -2152,11 +2178,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, case PW_CODE_ACCOUNTING_REQUEST: case PW_CODE_DISCONNECT_REQUEST: - case PW_CODE_DISCONNECT_ACK: - case PW_CODE_DISCONNECT_NAK: case PW_CODE_COA_REQUEST: - case PW_CODE_COA_ACK: - case PW_CODE_COA_NAK: memset(hdr->vector, 0, AUTH_VECTOR_LEN); break; @@ -2164,6 +2186,10 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, case PW_CODE_ACCESS_ACCEPT: case PW_CODE_ACCESS_REJECT: case PW_CODE_ACCESS_CHALLENGE: + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_ACK: + case PW_CODE_COA_NAK: memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); break; @@ -2510,6 +2536,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) char host_ipaddr[128]; #ifndef WITH_RADIUSV11_ONLY bool require_ma = false; + bool limit_proxy_state = false; + bool seen_proxy_state = false; bool seen_ma = false; bool eap = false; bool non_eap = false; @@ -2559,15 +2587,23 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) } /* - * Message-Authenticator is required in Status-Server - * packets, otherwise they can be trivially forged. + * If the caller requires Message-Authenticator, then set + * the flag. + * + * We also require Message-Authenticator if the packet + * code is Status-Server. + * + * If we're receiving packets from a proxy socket, then + * require Message-Authenticator for Access-* replies, + * and for Protocol-Error. */ - if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true; + require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]); /* - * It's also required if the caller asks for it. + * We only limit Proxy-State if we're not requiring + * Message-Authenticator. */ - if (flags) require_ma = true; + limit_proxy_state = ((flags & 0x04) != 0) && !require_ma; /* * Repeat the length checks. This time, instead of @@ -2723,6 +2759,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) case PW_EAP_MESSAGE: require_ma = true; eap = true; + packet->eap_message = true; break; case PW_USER_PASSWORD: @@ -2731,6 +2768,11 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) non_eap = true; break; + case PW_PROXY_STATE: + seen_proxy_state = true; + packet->proxy_state = true; + break; + case PW_MESSAGE_AUTHENTICATOR: #ifdef WITH_RADIUSV11 /* @@ -2749,6 +2791,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) goto finish; } seen_ma = true; + packet->message_authenticator = true; break; } #endif @@ -2813,7 +2856,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) !packet->radiusv11 && #endif !seen_ma) { - FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute", + FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute. You may need to set \"require_message_authenticator = no\" in the configuration.", inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, host_ipaddr, sizeof(host_ipaddr))); @@ -2822,6 +2865,18 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) } #ifndef WITH_RADIUSV11_ONLY + /* + * The client is a NAS which shouldn't send Proxy-State, but it did! + */ + if (limit_proxy_state && seen_proxy_state && !seen_ma) { + FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_MA_MISSING; + goto finish; + } + if (eap && non_eap) { FR_DEBUG_STRERROR_PRINTF("Bad packet from host %s: Packet contains EAP-Message and non-EAP authentication attribute", inet_ntop(packet->src_ipaddr.af, @@ -3938,7 +3993,7 @@ ssize_t data2vp(TALLOC_CTX *ctx, VALUE_PAIR *vp; uint8_t const *data = start; char *p; - uint8_t buffer[256]; + uint8_t buffer[MAX_PASS_LEN]; /* * FIXME: Attrlen can be larger than 253 for extended attrs! @@ -4054,7 +4109,7 @@ ssize_t data2vp(TALLOC_CTX *ctx, attrlen, secret, packet->vector); } - buffer[253] = '\0'; + buffer[attrlen] = '\0'; /* * MS-CHAP-MPPE-Keys are 24 octets, and @@ -4654,22 +4709,24 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, ssize_t my_len; #ifdef WITH_RADIUSV11 - /* - * Don't decode Message-Authenticator - */ - if (ptr[0] == PW_MESSAGE_AUTHENTICATOR) { - packet_length -= ptr[1]; - ptr += ptr[1]; - continue; - } + if (packet->radiusv11) { + /* + * Don't decode Message-Authenticator + */ + if (ptr[0] == PW_MESSAGE_AUTHENTICATOR) { + packet_length -= ptr[1]; + ptr += ptr[1]; + continue; + } - /* - * Don't decode Original-Packet-Code - */ - if ((ptr[0] == PW_EXTENDED_ATTRIBUTE_1) && (ptr[1] >= 3) && (ptr[2] == 4)) { - packet_length -= ptr[1]; - ptr += ptr[1]; - continue; + /* + * Don't decode Original-Packet-Code + */ + if ((ptr[0] == PW_EXTENDED_ATTRIBUTE_1) && (ptr[1] >= 3) && (ptr[2] == 4)) { + packet_length -= ptr[1]; + ptr += ptr[1]; + continue; + } } #endif @@ -4761,7 +4818,7 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, */ len = *pwlen; - if (len > 128) len = 128; + if (len > MAX_STRING_LEN) len = MAX_STRING_LEN; if (len == 0) { memset(passwd, 0, AUTH_PASS_LEN); @@ -4821,13 +4878,6 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, size_t n, secretlen; /* - * The RFC's say that the maximum is 128. - * The buffer we're putting it into above is 254, so - * we don't need to do any length checking. - */ - if (pwlen > 128) pwlen = 128; - - /* * Catch idiots. */ if (pwlen == 0) goto done; diff --git a/src/main/all.mk b/src/main/all.mk index 2517cd2..f3db386 100644 --- a/src/main/all.mk +++ b/src/main/all.mk @@ -1,3 +1,3 @@ SUBMAKEFILES := radclient.mk radiusd.mk radsniff.mk radmin.mk radattr.mk \ - radwho.mk radlast.mk radtest.mk radzap.mk checkrad.mk \ + radwho.mk radlast.mk radtest.mk radzap.mk checkrad.mk radsecret.mk \ libfreeradius-server.mk unittest.mk diff --git a/src/main/auth.c b/src/main/auth.c index 84889b8..2dc3e60 100644 --- a/src/main/auth.c +++ b/src/main/auth.c @@ -850,8 +850,8 @@ int rad_virtual_server(REQUEST *request) break; case PW_AUTH_TYPE_REJECT: - request->reply->code = PW_CODE_ACCESS_REJECT; - break; + request->reply->code = PW_CODE_ACCESS_REJECT; + break; default: break; @@ -864,6 +864,12 @@ int rad_virtual_server(REQUEST *request) if (vp) rad_postauth(request); } + if (request->reply->code == PW_CODE_ACCESS_CHALLENGE) { + fr_pair_delete_by_num(&request->config, PW_POST_AUTH_TYPE, 0, TAG_ANY); + vp = pair_make_config("Post-Auth-Type", "Challenge", T_OP_SET); + if (vp) rad_postauth(request); + } + if (request->reply->code == PW_CODE_ACCESS_ACCEPT) { /* * Check that there is a name which can be used diff --git a/src/main/cb.c b/src/main/cb.c index db764aa..65e484f 100644 --- a/src/main/cb.c +++ b/src/main/cb.c @@ -31,6 +31,7 @@ void cbtls_info(SSL const *s, int where, int ret) { char const *role, *state; REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); + fr_tls_server_conf_t *conf = (fr_tls_server_conf_t *) SSL_get_ex_data(s, FR_TLS_EX_INDEX_CONF); if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) { role = "Client "; @@ -58,7 +59,7 @@ void cbtls_info(SSL const *s, int where, int ret) len = strlen(abbrv); if ((len > 1) && (abbrv[len - 1] == ' ')) len--; - RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)", + RDEBUG3("(TLS) %s - Handshake state [%.*s] - %s%s (%d)", conf->name, (int)len, abbrv, role, state, SSL_get_state(s)); /* @@ -82,7 +83,7 @@ void cbtls_info(SSL const *s, int where, int ret) client_ciphers = SSL_get_client_ciphers(s); if (client_ciphers) { - RDEBUG3("Client preferred ciphers (by priority)"); + RDEBUG3("(TLS) %s - Client preferred ciphers (by priority)", conf->name); num_ciphers = sk_SSL_CIPHER_num(client_ciphers); for (i = 0; i < num_ciphers; i++) { this_cipher = sk_SSL_CIPHER_value(client_ciphers, i); @@ -92,7 +93,7 @@ void cbtls_info(SSL const *s, int where, int ret) } #endif } else { - RDEBUG2("(TLS) Handshake state - %s%s", role, state); + RDEBUG2("(TLS) %s - Handshake state - %s%s", conf->name, role, state); } return; } @@ -100,23 +101,27 @@ void cbtls_info(SSL const *s, int where, int ret) if (where & SSL_CB_ALERT) { if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return; - RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", + RERROR("(TLS) %s - Alert %s:%s:%s", conf->name, (where & SSL_CB_READ) ? "read": "write", SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); return; } if (where & SSL_CB_EXIT) { if (ret == 0) { - RERROR("(TLS) %s: Failed in %s", role, state); + RERROR("(TLS) %s - %s: Failed in %s", conf->name, role, state); return; } if (ret < 0) { if (SSL_want_read(s)) { - RDEBUG2("(TLS) %s: Need to read more data: %s", role, state); + RDEBUG2("(TLS) %s - %s: Need to read more data: %s", conf->name, role, state); return; } - RERROR("(TLS) %s: Error in %s", role, state); + if (SSL_want_write(s)) { + RDEBUG2("(TLS) %s - %s: Need to write more data: %s", conf->name, role, state); + return; + } + RERROR("(TLS) %s - %s: Error in %s", conf->name, role, state); } } } diff --git a/src/main/client.c b/src/main/client.c index 12f7824..58f9faa 100644 --- a/src/main/client.c +++ b/src/main/client.c @@ -328,7 +328,8 @@ check_list: (old->coa_home_server == client->coa_home_server) && (old->coa_home_pool == client->coa_home_pool) && #endif - (old->message_authenticator == client->message_authenticator)) { + (old->require_ma == client->require_ma) && + (old->limit_proxy_state == client->limit_proxy_state)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return true; @@ -490,6 +491,8 @@ static fr_ipaddr_t cl_ipaddr; static uint32_t cl_netmask; static char const *cl_srcipaddr = NULL; static char const *hs_proto = NULL; +static char const *require_message_authenticator = NULL; +static char const *limit_proxy_state = NULL; #ifdef WITH_TCP static CONF_PARSER limit_config[] = { @@ -512,7 +515,8 @@ static const CONF_PARSER client_config[] = { { "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL }, - { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" }, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, + { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL }, { "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL }, { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL }, @@ -724,7 +728,7 @@ static const CONF_PARSER dynamic_config[] = { { "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL }, { "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL }, - { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL }, + { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL }, { "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" }, { "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" }, @@ -906,8 +910,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo c = talloc_zero(ctx, RADCLIENT); c->cs = cs; + /* + * Set the "require message authenticator" and "limit + * proxy state" flags from the global default. If the + * configuration item exists, AND is set, it will + * over-ride the flag. + */ + c->require_ma = main_config.require_ma; + c->limit_proxy_state = main_config.limit_proxy_state; + memset(&cl_ipaddr, 0, sizeof(cl_ipaddr)); cl_netmask = 255; + require_message_authenticator = NULL; + limit_proxy_state = NULL; if (cf_section_parse(cs, c, client_config) < 0) { cf_log_err_cs(cs, "Error parsing client section"); @@ -917,6 +932,8 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo hs_proto = NULL; cl_srcipaddr = NULL; #endif + require_message_authenticator = NULL; + limit_proxy_state = NULL; return NULL; } @@ -1189,6 +1206,16 @@ done_coa: } #endif + if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) { + goto error; + } + + if (c->require_ma != FR_BOOL_TRUE) { + if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) { + goto error; + } + } + return c; } @@ -1233,7 +1260,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons if (shortname) c->shortname = talloc_typed_strdup(c, shortname); if (type) c->nas_type = talloc_typed_strdup(c, type); if (server) c->server = talloc_typed_strdup(c, server); - c->message_authenticator = require_ma; + c->require_ma = require_ma; return c; } @@ -1419,10 +1446,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) *pi = vp->vp_integer; /* - * Same nastiness as above. + * Same nastiness as above, but hard-coded for require Message-Authenticator. */ for (parse = client_config; parse->name; parse++) { - if (parse->offset == dynamic_config[i].offset) break; + if (parse->type == PW_TYPE_BOOLEAN) break; } if (!parse) break; @@ -1513,6 +1540,11 @@ validate: goto error; } + /* + * It can't be set to "auto". Too bad. + */ + c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma; + if (!client_add_dynamic(clients, request->client, c)) { return NULL; } diff --git a/src/main/command.c b/src/main/command.c index 988f43b..266366b 100644 --- a/src/main/command.c +++ b/src/main/command.c @@ -2701,7 +2701,7 @@ static int command_stats_socket(rad_listen_t *listener, int argc, char *argv[]) return command_print_stats(listener, &sock->stats, auth, 0); } -static int command_stats_pool(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) +static int command_stats_pool(rad_listen_t *listener, int argc, char *argv[]) { CONF_SECTION *cs; module_instance_t *mi; diff --git a/src/main/conffile.c b/src/main/conffile.c index 7bb206c..ad5a5fe 100644 --- a/src/main/conffile.c +++ b/src/main/conffile.c @@ -1424,12 +1424,13 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d { int rcode; bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists; + bool ignore_dflt; char **q; char const *value; CONF_PAIR *cp = NULL; fr_ipaddr_t *ipaddr; - char buffer[8192]; CONF_ITEM *c_item; + char buffer[8192]; if (!cs) { cf_log_err(&(cs->item), "No enclosing section for configuration item \"%s\"", name); @@ -1447,6 +1448,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d cant_be_empty = (type & PW_TYPE_NOT_EMPTY); tmpl = (type & PW_TYPE_TMPL); multi = (type & PW_TYPE_MULTI); + ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT); if (attribute) required = true; if (required) cant_be_empty = true; /* May want to review this in the future... */ @@ -1470,7 +1472,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d * section, use the default value. */ if (!cp) { - if (deprecated) return 0; /* Don't set the default value */ + if (deprecated || ignore_dflt) return 0; /* Don't set the default value */ rcode = 1; value = dflt; @@ -1658,6 +1660,62 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d cf_log_err(&(cs->item),"Failed expanding variable %s", name); return -1; } + + } else if (cf_new_escape && (cp->rhs_type == T_DOUBLE_QUOTED_STRING) && (strchr(value, '\\') != NULL)) { + char const *p = value; + char *s = buffer; + char *end = buffer + sizeof(buffer); + unsigned int x; + + /* + * We pass !cf_new_escape() to gettoken() when we parse the RHS of a CONF_PAIR + * above. But gettoken() unescapes the \", and doesn't unescape anything else. + * So we do it here. + */ + while (*p && (s < end)) { + if (*p != '\\') { + *(s++) = *(p++); + continue; + } + + p++; + + switch (*p) { + case 'r': + *s++ = '\r'; + break; + case 'n': + *s++ = '\n'; + break; + case 't': + *s++ = '\t'; + break; + + default: + if (*p >= '0' && *p <= '9' && + sscanf(p, "%3o", &x) == 1) { + if (!x) { + cf_log_err(&(cs->item), "Cannot have embedded zeros in value for %s", name); + return -1; + } + + *s++ = x; + p += 2; + } else + *s++ = *p; + break; + } + p++; + } + + if (s == end) { + cf_log_err(&(cs->item), "Failed expanding value for %s", name); + return -1; + } + + *s = '\0'; + + value = buffer; } if (cant_be_empty && (value[0] == '\0')) goto cant_be_empty; diff --git a/src/main/detail.c b/src/main/detail.c index a5e8437..3b7e382 100644 --- a/src/main/detail.c +++ b/src/main/detail.c @@ -910,8 +910,16 @@ open_file: rad_assert(vp != NULL); fr_pair_add(&packet->vps, vp); } + + /* + * Update Acct-Delay-Time, but make sure that it doesn't go backwards. + */ if (data->timestamp != 0) { - vp->vp_integer += time(NULL) - data->timestamp; + time_t now = time(NULL); + + if (((time_t) data->timestamp) < now) { + vp->vp_integer += time(NULL) - data->timestamp; + } } } diff --git a/src/main/exfile.c b/src/main/exfile.c index 59e6a05..1b498ce 100644 --- a/src/main/exfile.c +++ b/src/main/exfile.c @@ -170,7 +170,20 @@ static int exfile_open_mkdir(exfile_t *ef, char const *filename, mode_t permissi oflag = O_RDWR; } - fd = open(filename, oflag, permissions); + /* + * Just dup stdout / stderr if it's possible. + */ + if ((default_log.dst == L_DST_STDOUT) && + (strcmp(filename, "/dev/stdout") == 0)) { + fd = dup(STDOUT_FILENO); + + } else if ((default_log.dst == L_DST_STDERR) && + (strcmp(filename, "/dev/stderr") == 0)) { + fd = dup(STDERR_FILENO); + } else { + fd = open(filename, oflag, permissions); + } + if (fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); diff --git a/src/main/listen.c b/src/main/listen.c index ee73a57..a6ff080 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -55,7 +55,7 @@ RCSID("$Id$") #ifdef WITH_TLS #include <netinet/tcp.h> -# ifdef __APPLE__ +# if defined(__APPLE__) || defined(__FreeBSD__) || defined(__illumos__) || defined(__sun__) # if !defined(SOL_TCP) && defined(IPPROTO_TCP) # define SOL_TCP IPPROTO_TCP # endif @@ -385,6 +385,7 @@ int rad_status_server(REQUEST *request) if (sock->state == LISTEN_TLS_CHECKING) { int autz_type = PW_AUTZ_TYPE; char const *name = "Autz-Type"; + rad_listen_t *listener = request->listener; if (request->listener->type == RAD_LISTEN_ACCT) { autz_type = PW_ACCT_TYPE; @@ -404,11 +405,22 @@ int rad_status_server(REQUEST *request) if ((rcode == RLM_MODULE_OK) || (rcode == RLM_MODULE_UPDATED)) { RDEBUG("(TLS) Connection is authorized"); request->reply->code = PW_CODE_ACCESS_ACCEPT; + + listener->status = RAD_LISTEN_STATUS_RESUME; + + rad_assert(sock->request->packet != request->packet); + + sock->state = LISTEN_TLS_SETUP; + } else { RWDEBUG("(TLS) Connection is not authorized - closing TCP socket."); request->reply->code = PW_CODE_ACCESS_REJECT; + + listener->status = RAD_LISTEN_STATUS_EOL; + listener->tls = NULL; /* parent owns this! */ } + radius_update_listener(listener); return 0; } } @@ -518,6 +530,123 @@ int rad_status_server(REQUEST *request) return 0; } +static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client) +{ + if (client->require_ma == FR_BOOL_TRUE) return; + + if (client->require_ma == FR_BOOL_AUTO) { + if (!packet->message_authenticator) { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet without Message-Authenticator."); + ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + client->require_ma = FR_BOOL_FALSE; + + /* + * And fall through to the + * limit_proxy_state checks, which might + * complain again. Oh well, maybe that + * will make people read the messages. + */ + + } else if (packet->eap_message) { + /* + * Don't set it to "true" for packets + * with EAP-Message. It's already + * required there, and we might get a + * non-EAP packet with (or without) + * Message-Authenticator + */ + return; + } else { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet with Message-Authenticator."); + ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack."); + ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + client->require_ma = FR_BOOL_TRUE; + return; + } + + } + + /* + * If all of the checks are turned off, then complain for every packet we receive. + */ + if (client->limit_proxy_state == FR_BOOL_FALSE) { + /* + * We have a Message-Authenticator, and it's valid. We don't need to compain. + */ + if (packet->message_authenticator) return; + + if (!fr_debug_lvl) return; /* easier than checking for each line below */ + + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + DEBUG("BlastRADIUS check: Received packet without Message-Authenticator."); + DEBUG("YOU MUST SET \"require_message_authenticator = true\", or"); + DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname); + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + DEBUG("The packet does not contain Message-Authenticator, which is a security issue"); + DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + return; + } + + /* + * Don't complain here. rad_packet_ok() will instead + * complain about every packet with Proxy-State but which + * is missing Message-Authenticator. + */ + if (client->limit_proxy_state == FR_BOOL_TRUE) { + return; + } + + if (packet->proxy_state && !packet->message_authenticator) { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator."); + ERROR("This is either a BlastRADIUS attack, OR"); + ERROR("the client is a proxy RADIUS server which has not been upgraded."); + ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + client->limit_proxy_state = FR_BOOL_FALSE; + + } else { + client->limit_proxy_state = FR_BOOL_TRUE; + + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + if (!packet->proxy_state) { + ERROR("BlastRADIUS check: Received packet without Proxy-State."); + } else { + ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator."); + } + + ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + if (!packet->message_authenticator) { + ERROR("The packet does not contain Message-Authenticator, which is a security issue."); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + } else { + ERROR("The packet contains Message-Authenticator."); + if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack."); + ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); + } + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } +} + #ifdef WITH_TCP static int dual_tcp_recv(rad_listen_t *listener) { @@ -594,6 +723,21 @@ static int dual_tcp_recv(rad_listen_t *listener) switch (packet->code) { case PW_CODE_ACCESS_REQUEST: if (listener->type != RAD_LISTEN_AUTH) goto bad_packet; + + /* + * Enforce BlastRADIUS checks on TCP, too. + */ + if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) { + FR_STATS_INC(auth, total_malformed_requests); + rad_free(&sock->packet); + return 0; + } + + /* + * Perform BlastRADIUS checks and warnings. + */ + if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); + FR_STATS_INC(auth, total_requests); fun = rad_authenticate; break; @@ -720,7 +864,12 @@ static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) #endif #ifdef WITH_RADIUSV11 -static const unsigned char radiusv11_alpn_protos[] = { +static const unsigned char radiusv11_allow_protos[] = { + 10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '1', /* prefer this */ + 10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '0', +}; + +static const unsigned char radiusv11_require_protos[] = { 10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '1', }; @@ -750,23 +899,28 @@ static int radiusv11_server_alpn_cb(SSL *ssl, memcpy(&hack, &out, sizeof(out)); /* const issues */ /* - * The RADIUSv11 configuration for this socket is a combination of what we require, and what we + * The RADIUS/1.1 configuration for this socket is a combination of what we require, and what we * require of the client. */ switch (this->radiusv11) { /* - * If we forbid RADIUSv11, then we never advertised it via ALPN, and this callback should + * If we forbid RADIUS/1.1, then we never advertised it via ALPN, and this callback should * never have been registered. */ case FR_RADIUSV11_FORBID: - *out = NULL; - *outlen = 0; - return SSL_TLSEXT_ERR_OK; + fr_assert(0); + server = radiusv11_allow_protos + 11; + server_len = 11; + break; case FR_RADIUSV11_ALLOW: + server = radiusv11_allow_protos; + server_len = sizeof(radiusv11_allow_protos); + break; + case FR_RADIUSV11_REQUIRE: - server = radiusv11_alpn_protos; - server_len = sizeof(radiusv11_alpn_protos); + server = radiusv11_require_protos; + server_len = sizeof(radiusv11_require_protos); break; } @@ -786,6 +940,7 @@ static int radiusv11_server_alpn_cb(SSL *ssl, */ fr_assert(*outlen == 10); sock->radiusv11 = (server[9] == '1'); + sock->alpn_checked = true; RDEBUG("(TLS) ALPN server negotiated application protocol \"%.*s\"", (int) *outlen, server); return SSL_TLSEXT_ERR_OK; @@ -798,6 +953,26 @@ static int radiusv11_server_alpn_cb(SSL *ssl, return SSL_TLSEXT_ERR_ALERT_FATAL; } +static int radiusv11_client_hello_cb(UNUSED SSL *s, int *alert, void *arg) +{ + rad_listen_t *this = arg; + listen_socket_t *sock = this->data; + + /* + * The server_alpn_cb ran, and checked that the configured ALPN matches the negotiated one. + */ + if (sock->alpn_checked) return SSL_CLIENT_HELLO_SUCCESS; + + /* + * The server_alpn_cb did NOT run (???) but we still have a client hello. We require ALPN and + * none was negotiated, so we return an error. + */ + *alert = SSL_AD_NO_APPLICATION_PROTOCOL; + + return SSL_CLIENT_HELLO_ERROR; +} + + int fr_radiusv11_client_init(fr_tls_server_conf_t *tls); int fr_radiusv11_client_get_alpn(rad_listen_t *listener); @@ -805,13 +980,17 @@ int fr_radiusv11_client_init(fr_tls_server_conf_t *tls) { switch (tls->radiusv11) { case FR_RADIUSV11_ALLOW: - case FR_RADIUSV11_REQUIRE: - if (SSL_CTX_set_alpn_protos(tls->ctx, radiusv11_alpn_protos, sizeof(radiusv11_alpn_protos)) != 0) { - ERROR("Failed setting RADIUSv11 negotiation flags"); + if (SSL_CTX_set_alpn_protos(tls->ctx, radiusv11_allow_protos, sizeof(radiusv11_allow_protos)) != 0) { + fail_protos: + ERROR("Failed setting RADIUS/1.1 negotiation flags"); return -1; } break; + case FR_RADIUSV11_REQUIRE: + if (SSL_CTX_set_alpn_protos(tls->ctx, radiusv11_require_protos, sizeof(radiusv11_require_protos)) != 0) goto fail_protos; + break; + default: break; } @@ -827,41 +1006,53 @@ int fr_radiusv11_client_get_alpn(rad_listen_t *listener) SSL_get0_alpn_selected(sock->ssn->ssl, &data, &len); if (!data) { - DEBUG("(TLS) ALPN home server did not send any application protocol"); + DEBUG("(TLS) ALPN server did not send any application protocol"); if (listener->radiusv11 == FR_RADIUSV11_REQUIRE) { DEBUG("(TLS) We have 'radiusv11 = require', but the home server has not negotiated it - closing socket"); return -1; } - DEBUG("(TLS) ALPN assuming historical RADIUS"); - return 0; + DEBUG("(TLS) ALPN assuming \"radius/1.0\""); + return 0; /* allow radius/1.0 */ } - DEBUG("(TLS) ALPN home server sent application protocol \"%.*s\"", (int) len, data); + DEBUG("(TLS) ALPN server sent application protocol \"%.*s\"", (int) len, data); if (len != 10) { radiusv11_unknown: - DEBUG("(TLS) ALPN home server sent unknown application protocol - closing connection"); + DEBUG("(TLS) ALPN server sent unknown application protocol - closing connection to home server"); return -1; } /* - * Should always be "radius/1.1". The server MUST echo back one of the strings + * Should always be "radius/1.0" or "radius/1.1". The server MUST echo back one of the strings * we sent. If it doesn't, it's a bad server. */ - if (memcmp(data, "radius/1.1", 10) != 0) goto radiusv11_unknown; + if (memcmp(data, "radius/1.", 9) != 0) goto radiusv11_unknown; + + if ((data[9] != '0') && (data[9] != '1')) goto radiusv11_unknown; /* * Double-check what the server sent us. It SHOULD be sane, but it never hurts to check. */ switch (listener->radiusv11) { case FR_RADIUSV11_FORBID: - DEBUG("(TLS) ALPN home server sent \"radius/v1.1\" but we forbid it - closing connection to home server"); - return -1; + if (data[9] != '0') { + DEBUG("(TLS) ALPN server did not send \"radius/v1.0\" - closing connection to home server"); + return -1; + } + break; case FR_RADIUSV11_ALLOW: + sock->radiusv11 = (data[9] == '1'); + break; + case FR_RADIUSV11_REQUIRE: - DEBUG("(TLS) ALPN using \"radius/1.1\""); + if (data[9] != '1') { + DEBUG("(TLS) ALPN server did not send \"radius/v1.1\" - closing connection to home server"); + return -1; + } + sock->radiusv11 = true; break; } @@ -1066,15 +1257,30 @@ static int dual_tcp_accept(rad_listen_t *listener) SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); #ifdef WITH_RADIUSV11 - /* - * Default is "forbid" (0). In which case we don't set any ALPN callbacks, and - * the ServerHello does not contain an ALPN section. - */ - if (client->radiusv11 != FR_RADIUSV11_FORBID) { + switch (client->radiusv11) { + /* + * We don't set any callbacks. If the client sends ALPN (or not), we + * just do normal RADIUS. + */ + case FR_RADIUSV11_FORBID: + DEBUG("(TLS) ALPN radiusv11 = forbid"); + break; + + /* + * Setting the client hello callback catches the case where we send ALPN, + * and the client doesn't send anything. + */ + case FR_RADIUSV11_REQUIRE: + SSL_CTX_set_client_hello_cb(this->tls->ctx, radiusv11_client_hello_cb, this); + /* FALL-THROUGH */ + + /* + * We're willing to do normal RADIUS, but we send ALPN, and then check if + * (or what) the client sends back as ALPN. + */ + case FR_RADIUSV11_ALLOW: SSL_CTX_set_alpn_select_cb(this->tls->ctx, radiusv11_server_alpn_cb, this); DEBUG("(TLS) ALPN radiusv11 = allow / require"); - } else { - DEBUG("(TLS) ALPN radiusv11 = forbid"); } #endif } @@ -1313,6 +1519,12 @@ static CONF_PARSER limit_config[] = { { "max_connections", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.max_connections), "16" }, { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.lifetime), "0" }, { "idle_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.idle_timeout), STRINGIFY(30) }, +#ifdef SO_RCVTIMEO + { "read_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.read_timeout), NULL }, +#endif +#ifdef SO_SNDTIMEO + { "write_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, listen_socket_t, limit.write_timeout), NULL }, +#endif #endif CONF_PARSER_TERMINATOR }; @@ -1467,6 +1679,8 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) return -1; } + this->tls->name = "RADIUS/TLS"; + #ifdef HAVE_PTHREAD_H if (pthread_mutex_init(&sock->mutex, NULL) < 0) { rad_assert(0 == 1); @@ -1822,8 +2036,6 @@ static int stats_socket_recv(rad_listen_t *listener) rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code); if (rcode < 0) return 0; - FR_STATS_INC(auth, total_requests); - if (rcode < 20) { /* RADIUS_HDR_LEN */ if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); FR_STATS_INC(auth, total_malformed_requests); @@ -1871,7 +2083,6 @@ static int stats_socket_recv(rad_listen_t *listener) } #endif - /* * Check if an incoming request is "ok" * @@ -1947,7 +2158,7 @@ static int auth_socket_recv(rad_listen_t *listener) * Now that we've sanity checked everything, receive the * packet. */ - packet = rad_recv(ctx, listener->fd, client->message_authenticator); + packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2)); if (!packet) { FR_STATS_INC(auth, total_malformed_requests); if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -1955,6 +2166,11 @@ static int auth_socket_recv(rad_listen_t *listener) return 0; } + /* + * Perform BlastRADIUS checks and warnings. + */ + if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); + #ifdef __APPLE__ #ifdef WITH_UDPFROMTO /* @@ -2343,7 +2559,7 @@ static int coa_socket_recv(rad_listen_t *listener) * Now that we've sanity checked everything, receive the * packet. */ - packet = rad_recv(ctx, listener->fd, client->message_authenticator); + packet = rad_recv(ctx, listener->fd, client->require_ma); if (!packet) { FR_STATS_INC(coa, total_malformed_requests); if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -2663,7 +2879,7 @@ static int proxy_socket_encode(RADIUSV11_UNUSED rad_listen_t *listener, REQUEST } -static int proxy_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) +static int proxy_socket_decode(RADIUSV11_UNUSED rad_listen_t *listener, REQUEST *request) { #ifdef WITH_RADIUSV11 listen_socket_t *sock = listener->data; @@ -2907,6 +3123,9 @@ static int listen_bind(rad_listen_t *this) */ if (sock->interface) { #ifdef SO_BINDTODEVICE + /* + * Linux: Bind to an interface by name. + */ struct ifreq ifreq; memset(&ifreq, 0, sizeof(ifreq)); @@ -2919,45 +3138,81 @@ static int listen_bind(rad_listen_t *this) if (rcode < 0) { close(this->fd); ERROR("Failed binding to interface %s: %s", - sock->interface, fr_syserror(errno)); + sock->interface, fr_syserror(errno)); return -1; - } /* else it worked. */ + } #else + + /* + * If we don't bind to an interface by name, we usually bind to it by index. + */ + int idx = if_nametoindex(sock->interface); + + if (idx == 0) { + close(this->fd); + ERROR("Failed finding interface %s: %s", + sock->interface, fr_syserror(errno)); + return -1; + } + +#ifdef IP_BOUND_IF + /* + * OSX / ?BSD / Solaris: bind to interface by index for IPv4 + */ + if (sock->my_ipaddr.af == AF_INET) { + rad_suid_up(); + rcode = setsockopt(this->fd, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)); + rad_suid_down(); + if (rcode < 0) { + close(this->fd); + ERROR("Failed binding to interface %s: %s", + sock->interface, fr_syserror(errno)); + return -1; + } + } else +#endif + +#ifdef IPV6_BOUND_IF + /* + * OSX / ?BSD / Solaris: bind to interface by index for IPv6 + */ + if (sock->my_ipaddr.af == AF_INET6) { + rad_suid_up(); + rcode = setsockopt(this->fd, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx)); + rad_suid_down(); + if (rcode < 0) { + close(this->fd); + ERROR("Failed binding to interface %s: %s", + sock->interface, fr_syserror(errno)); + return -1; + } + } else +#endif + #ifdef HAVE_STRUCT_SOCKADDR_IN6 #ifdef HAVE_NET_IF_H /* - * Odds are that any system supporting "bind to - * device" also supports IPv6, so this next bit - * isn't necessary. But it's here for - * completeness. - * - * If we're doing IPv6, and the scope hasn't yet - * been defined, set the scope to the scope of - * the interface. + * Otherwise generic IPv6: set the scope to the + * interface, and hope that all of the read/write + * routines respect that. */ if (sock->my_ipaddr.af == AF_INET6) { if (sock->my_ipaddr.scope == 0) { - sock->my_ipaddr.scope = if_nametoindex(sock->interface); - if (sock->my_ipaddr.scope == 0) { - close(this->fd); - ERROR("Failed finding interface %s: %s", - sock->interface, fr_syserror(errno)); - return -1; - } - } /* else scope was defined: we're OK. */ + sock->my_ipaddr.scope = idx; + } /* else scope was already defined */ } else #endif #endif - /* - * IPv4: no link local addresses, - * and no bind to device. - */ + + /* + * IPv4, or no socket options to bind to interface. + */ { close(this->fd); ERROR("Failed binding to interface %s: \"bind to device\" is unsupported", sock->interface); return -1; } -#endif +#endif /* SO_BINDTODEVICE */ } #ifdef WITH_TCP @@ -3067,6 +3322,7 @@ static int listen_bind(rad_listen_t *this) int on = 1; if (setsockopt(this->fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { + close(this->fd); ERROR("Can't set broadcast option: %s", fr_syserror(errno)); return -1; @@ -3115,6 +3371,7 @@ static int listen_bind(rad_listen_t *this) memset(&src, 0, sizeof_src); if (getsockname(this->fd, (struct sockaddr *) &src, &sizeof_src) < 0) { + close(this->fd); ERROR("Failed getting socket name: %s", fr_syserror(errno)); return -1; @@ -3122,6 +3379,7 @@ static int listen_bind(rad_listen_t *this) if (!fr_sockaddr2ipaddr(&src, sizeof_src, &sock->my_ipaddr, &sock->my_port)) { + close(this->fd); ERROR("Socket has unsupported address family"); return -1; } @@ -3131,9 +3389,9 @@ static int listen_bind(rad_listen_t *this) #ifdef WITH_TCP if (sock->proto == IPPROTO_TCP) { /* - * Woker threads are blocking. + * If we dedicate a worker thread to each socket, then the socket is blocking. * - * Otherwise, they're non-blocking. + * Otherwise, all input TCP sockets are non-blocking. */ if (!this->workers) { if (fr_nonblock(this->fd) < 0) { @@ -3331,11 +3589,15 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t * FIXME: connect() is blocking! * We do this with the proxy mutex locked, which may * cause large delays! - * - * http://www.developerweb.net/forum/showthread.php?p=13486 */ this->fd = fr_socket_client_tcp(&home->src_ipaddr, - &home->ipaddr, home->port, false); + &home->ipaddr, home->port, +#ifdef WITH_TLS + !this->nonblock +#else + false +#endif + ); /* * Set max_requests, lifetime, and idle_timeout from the home server. @@ -3378,10 +3640,25 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t this->nonblock |= home->nonblock; +#ifdef TCP_NODELAY + /* + * Also set TCP_NODELAY, to force the data to be written quickly. + */ + if (sock->proto == IPPROTO_TCP) { + int on = 1; + + if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { + ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); + goto error; + } + } +#endif + /* * Set non-blocking if it's configured. */ if (this->nonblock) { + fr_assert(0); if (fr_nonblock(this->fd) < 0) { ERROR("(TLS) Failed setting nonblocking for proxy socket '%s' - %s", buffer, fr_strerror()); goto error; @@ -3394,15 +3671,34 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t goto error; } -#ifdef TCP_NODELAY + } else { /* - * Also set TCP_NODELAY, to force the data to be written quickly. + * Only set timeouts when the socket is blocking. This allows blocking + * sockets to still time out when the underlying socket is dead. */ - if (sock->proto == IPPROTO_TCP) { - int on = 1; +#ifdef SO_RCVTIMEO + if (sock->limit.read_timeout) { + struct timeval tv; + + tv.tv_sec = sock->limit.read_timeout; + tv.tv_usec = 0; - if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { - ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); + if (setsockopt(this->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + ERROR("(TLS) Failed to set read_timeout: %s", fr_syserror(errno)); + goto error; + } + } +#endif + +#ifdef SO_SNDTIMEO + if (sock->limit.write_timeout) { + struct timeval tv; + + tv.tv_sec = sock->limit.write_timeout; + tv.tv_usec = 0; + + if (setsockopt(this->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { + ERROR("(TLS) Failed to set write_timeout: %s", fr_syserror(errno)); goto error; } } @@ -3427,6 +3723,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t } #endif + sock->connect_timeout = home->connect_timeout; this->recv = proxy_tls_recv; @@ -3575,7 +3872,9 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) char const *value; fr_dlhandle handle; CONF_SECTION *server_cs; +#ifdef WITH_TCP char const *p; +#endif char buffer[32]; cp = cf_pair_find(cs, "type"); @@ -3864,7 +4163,9 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head, bool spawn_flag) if (override) { cs = cf_section_sub_find_name2(config, "server", main_config.name); - if (cs) this->server = main_config.name; + if (!cs) cs = cf_section_sub_find_name2(config, "server", + "default"); + if (cs) this->server = cf_section_name2(cs); } *last = this; diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c index 227ae4a..2b2dda8 100644 --- a/src/main/mainconfig.c +++ b/src/main/mainconfig.c @@ -73,6 +73,8 @@ static char const *gid_name = NULL; static char const *chroot_dir = NULL; static bool allow_core_dumps = false; static char const *radlog_dest = NULL; +static char const *require_message_authenticator = NULL; +static char const *limit_proxy_state = NULL; /* * These are not used anywhere else.. @@ -87,6 +89,56 @@ static bool do_colourise = false; static char const *radius_dir = NULL; //!< Path to raddb directory +#ifndef HAVE_KQUEUE +static uint32_t max_fds = 0; +#endif + +static const FR_NAME_NUMBER fr_bool_auto_names[] = { + { "false", FR_BOOL_FALSE }, + { "no", FR_BOOL_FALSE }, + { "0", FR_BOOL_FALSE }, + + { "true", FR_BOOL_TRUE }, + { "yes", FR_BOOL_TRUE }, + { "1", FR_BOOL_TRUE }, + + { "auto", FR_BOOL_AUTO }, + + { NULL, 0 } +}; + +/* + * Get decent values for false / true / auto + */ +int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str) +{ + int value; + + /* + * Don't change anything. + */ + if (!str) return 0; + + value = fr_str2int(fr_bool_auto_names, str, -1); + if (value >= 0) { + *out = value; + return 0; + } + + /* + * This should never happen, as the defaults are in the + * source code. If there's no CONF_PAIR, and there's a + * parse error, then the source code is wrong. + */ + if (!cp) { + fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name); + return -1; + } + + cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp)); + return -1; +} + /********************************************************************** * * We need to figure out where the logs go, before doing anything @@ -160,6 +212,8 @@ static const CONF_PARSER security_config[] = { { "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) }, { "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) }, { "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"}, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"}, + { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"}, #ifdef ENABLE_OPENSSL_VERSION_CHECK { "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"}, #endif @@ -195,8 +249,12 @@ static const CONF_PARSER server_config[] = { { "panic_action", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.panic_action), NULL}, { "hostname_lookups", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &fr_dns_lookups), "no" }, { "max_request_time", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_request_time), STRINGIFY(MAX_REQUEST_TIME) }, + { "proxy_dedup_window", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.proxy_dedup_window), "1" }, { "cleanup_delay", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.cleanup_delay), STRINGIFY(CLEANUP_DELAY) }, { "max_requests", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_requests), STRINGIFY(MAX_REQUESTS) }, +#ifndef HAVE_KQUEUE + { "max_fds", FR_CONF_POINTER(PW_TYPE_INTEGER, &max_fds), "512" }, +#endif { "postauth_client_lost", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.postauth_client_lost), "no" }, { "pidfile", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.pid_file), "${run_dir}/radiusd.pid"}, { "checkrad", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.checkrad), "${sbindir}/checkrad" }, @@ -864,6 +922,8 @@ int main_config_init(void) if (!main_config.dictionary_dir) { main_config.dictionary_dir = DICTDIR; } + main_config.require_ma = FR_BOOL_AUTO; + main_config.limit_proxy_state = FR_BOOL_AUTO; /* * About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400 @@ -1144,6 +1204,10 @@ do {\ if ((main_config.reject_delay.tv_sec != 0) || (main_config.reject_delay.tv_usec != 0)) { FR_TIMEVAL_BOUND_CHECK("reject_delay", &main_config.reject_delay, >=, 1, 0); } + + FR_INTEGER_BOUND_CHECK("proxy_dedup_window", main_config.proxy_dedup_window, <=, 10); + FR_INTEGER_BOUND_CHECK("proxy_dedup_window", main_config.proxy_dedup_window, >=, 1); + FR_TIMEVAL_BOUND_CHECK("reject_delay", &main_config.reject_delay, <=, 10, 0); FR_INTEGER_BOUND_CHECK("cleanup_delay", main_config.cleanup_delay, <=, 30); @@ -1159,6 +1223,46 @@ do {\ main_config.init_delay.tv_sec = 0; main_config.init_delay.tv_usec = 2* (1000000 / 3); + { + CONF_PAIR *cp = NULL; + + subcs = cf_section_sub_find(cs, "security"); + if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator"); + if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) { + cf_file_free(cs); + return -1; + } + + if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state"); + if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) { + cf_file_free(cs); + return -1; + } + } + +#ifndef HAVE_KQUEUE + /* + * select() is limited to 1024 file descriptors. :( + */ + if (max_fds) { + if (max_fds > FD_SETSIZE) { + fr_ev_max_fds = FD_SETSIZE; + } else { + /* + * Round up to the next highest power of 2. + */ + max_fds--; + max_fds |= max_fds >> 1; + max_fds |= max_fds >> 2; + max_fds |= max_fds >> 4; + max_fds |= max_fds >> 8; + max_fds |= max_fds >> 16; + max_fds++; + fr_ev_max_fds = max_fds; + } + } +#endif + /* * Free the old configuration items, and replace them * with the new ones. diff --git a/src/main/map.c b/src/main/map.c index e59fcec..34683a2 100644 --- a/src/main/map.c +++ b/src/main/map.c @@ -1108,7 +1108,7 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t */ if (((map->lhs->tmpl_list == PAIR_LIST_COA) || (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) { - if (request->parent) { + if (context->parent) { REDEBUG("You can only do 'update coa' when processing a packet which was received from the network"); return -2; } diff --git a/src/main/modcall.c b/src/main/modcall.c index aa6abf8..5a3116c 100644 --- a/src/main/modcall.c +++ b/src/main/modcall.c @@ -311,7 +311,7 @@ static rlm_rcode_t CC_HINT(nonnull) call_modsingle(rlm_components_t component, m */ blocked = (request->master_state == REQUEST_STOP_PROCESSING); if (blocked) { - RWARN("Module %s became unblocked", sp->modinst->entry->name); + RWARN("Module %s(%s) became unblocked", sp->modinst->name, sp->modinst->entry->name); } fail: diff --git a/src/main/modules.c b/src/main/modules.c index fd4334d..9ccb310 100644 --- a/src/main/modules.c +++ b/src/main/modules.c @@ -571,10 +571,10 @@ static int module_conf_parse(module_instance_t *node, void **handle) * If there is supposed to be instance data, allocate it now. * Also parse the configuration data, if required. */ - if (node->entry->module->inst_size) { - *handle = talloc_zero_array(node, uint8_t, node->entry->module->inst_size); - rad_assert(*handle); + *handle = talloc_zero_array(node, uint8_t, node->entry->module->inst_size); + rad_assert(*handle); + if (node->entry->module->inst_size) { talloc_set_name(*handle, "rlm_%s_t", node->entry->module->name ? node->entry->module->name : "config"); diff --git a/src/main/process.c b/src/main/process.c index ed77839..9880e34 100644 --- a/src/main/process.c +++ b/src/main/process.c @@ -1006,6 +1006,12 @@ static void request_cleanup_delay_init(REQUEST *request) #ifdef HAVE_PTHREAD_H rad_assert(request->child_pid == NO_SUCH_CHILD_PID); #endif + + /* + * Set the statistics immediately if we can. + */ + request_stats_final(request); + STATE_MACHINE_TIMER(FR_ACTION_TIMER); return; } @@ -1258,6 +1264,9 @@ static void request_cleanup_delay(REQUEST *request, int action) #ifdef DEBUG_STATE_MACHINE if (rad_debug_lvl) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_cleanup_delay"); #endif + + request_stats_final(request); + STATE_MACHINE_TIMER(FR_ACTION_TIMER); return; } /* else it's time to clean up */ @@ -1486,7 +1495,7 @@ static void request_finish(REQUEST *request, int action) /* * Maybe originate a CoA request. */ - if ((action == FR_ACTION_RUN) && !request->proxy && request->coa) { + if ((action == FR_ACTION_RUN) && (!request->proxy || request->proxy->dst_port == 0) && request->coa) { request_coa_originate(request); } #endif @@ -1591,9 +1600,14 @@ static void request_finish(REQUEST *request, int action) #ifdef WITH_PROXY /* * If we timed out a proxy packet, don't delay - * the reject any more. + * the reject any more. Or, if we proxied it to + * a real home server, then don't delay it. + * + * We don't want to have each proxy in a chain + * adding their own reject delay, which would + * result in N*reject_delays being applied. */ - if (request->proxy && !request->proxy_reply) { + if (request->proxy && (!request->proxy_reply || request->proxy->dst_port != 0)) { request->response_delay.tv_sec = 0; request->response_delay.tv_usec = 0; } @@ -2024,6 +2038,10 @@ static REQUEST *request_setup(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PA return NULL; } +#ifdef WITH_RADIUSV11 + request->reply->radiusv11 = packet->radiusv11; +#endif + request->listener = listener; request->client = client; request->packet = talloc_steal(request, packet); @@ -2286,16 +2304,6 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) if (!request->in_proxy_hash) return; -#ifdef COA_TUNNEL - /* - * Track how many IDs are used. This information - * helps the listen_coa_find() function get a - * listener which has free IDs. - */ - rad_assert(request->proxy_listener->num_ids_used > 0); - request->proxy_listener->num_ids_used--; -#endif - fr_packet_list_id_free(proxy_list, request->proxy, yank); request->in_proxy_hash = false; @@ -2341,6 +2349,18 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) if (request->proxy_listener) { request->proxy_listener->count--; + +#ifdef WITH_COA_TUNNEL + /* + * Track how many IDs are used. This information + * helps the listen_coa_find() function get a + * listener which has free IDs. + */ + if (request->proxy_listener->send_coa) { + rad_assert(request->proxy_listener->num_ids_used > 0); + request->proxy_listener->num_ids_used--; + } +#endif } request->proxy_listener = NULL; @@ -2361,18 +2381,6 @@ static void remove_from_proxy_hash(REQUEST *request) */ if (!request->in_proxy_hash) return; -#ifdef WITH_TCP - /* - * Status-Server packets aren't removed from the proxy hash. They're reused. - * - * Unless we're tearing down the listener. - */ - if ((request->proxy->proto == IPPROTO_TCP) && (request->proxy->code == PW_CODE_STATUS_SERVER) && - request->proxy_listener && (request->proxy_listener->status < RAD_LISTEN_STATUS_EOL)) { - return; - } -#endif - /* * The "not in hash" flag is definitive. However, if the * flag says that it IS in the hash, there might still be @@ -2493,13 +2501,13 @@ static int insert_into_proxy_hash(REQUEST *request) goto fail; } -#ifdef COA_TUNNEL +#ifdef WITH_COA_TUNNEL /* * Track how many IDs are used. This information * helps the listen_coa_find() function get a * listener which has free IDs. */ - request->proxy_listener->num_ids_used++; + if (request->proxy_listener->send_coa) request->proxy_listener->num_ids_used++; #endif /* @@ -2790,16 +2798,78 @@ int request_proxy_reply(RADIUS_PACKET *packet) * server core, but I guess we can fix that later. */ if (!request->proxy_reply) { + decode_fail_t reason; + + /* + * If the home server configuration requires a Message-Authenticator, then set the flag, + * but only if the proxied packet is Access-Request or Status-Sercer. + * + * The realms.c file already clears require_ma for TLS connections. + */ + bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST); + if (!request->home_server) { proxy_reply_too_late(request); return 0; } + if (!rad_packet_ok(packet, require_ma, &reason)) { + DEBUG("Ignoring invalid packet - %s", fr_strerror()); + return 0; + } + if (rad_verify(packet, request->proxy, request->home_server->secret) != 0) { DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); return 0; } + + /* + * BlastRADIUS checks. We're running in the main + * listener thread, so there's no conflict + * checking or setting these fields. + */ + if ((request->proxy->code == PW_CODE_ACCESS_REQUEST) && +#ifdef WITH_TLS + !request->home_server->tls && +#endif + !packet->eap_message) { + if (request->home_server->require_ma == FR_BOOL_AUTO) { + if (!packet->message_authenticator) { + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator."); + RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + request->home_server->require_ma = FR_BOOL_FALSE; + } else { + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator."); + RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack."); + RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + request->home_server->require_ma = FR_BOOL_TRUE; + } + + } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) { + /* + * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet. + */ + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name); + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RDEBUG("The packet does not contain Message-Authenticator, which is a security issue"); + RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + RDEBUG("Once the home server is upgraded, set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } + } } /* @@ -2863,6 +2933,18 @@ int request_proxy_reply(RADIUS_PACKET *packet) #ifdef WITH_STATS /* + * The average includes our time to receive packets and + * look them up in the hashes, which should be the same + * for all packets. + * + * We update the response time only for the FIRST packet + * we receive. + */ + if (request->home_server->ema.window > 0) { + radius_stats_ema(&request->home_server->ema, &request->proxy->timestamp, &now); + } + + /* * Update the proxy listener stats here, because only one * thread accesses that at a time. The home_server and * main proxy_*_stats structures are updated once the @@ -3193,6 +3275,14 @@ static int request_will_proxy(REQUEST *request) pool = home_pool_byname(vp->vp_strvalue, pool_type); /* + * If we didn't find an auth only or acct only pool + * fall-back to those which do both. + */ + if (!pool && ((pool_type == HOME_TYPE_AUTH) || (pool_type == HOME_TYPE_ACCT))) { + pool = home_pool_byname(vp->vp_strvalue, HOME_TYPE_AUTH_ACCT); + } + + /* * Send it directly to a home server (i.e. NAS) */ } else if (((vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IP_ADDRESS, 0, TAG_ANY)) != NULL) || @@ -3292,6 +3382,15 @@ static int request_will_proxy(REQUEST *request) * Find the home server by name. */ home = home_server_byname(vp->vp_strvalue, type); + + /* + * If we didn't find an auth only or acct only home server + * fall-back to those which do both. + */ + if (!home && ((type == HOME_TYPE_AUTH) || (type == HOME_TYPE_ACCT))) { + home = home_server_byname(vp->vp_strvalue, HOME_TYPE_AUTH_ACCT); + } + if (!home) { RWDEBUG("No such home server %s", vp->vp_strvalue); return 0; @@ -3361,7 +3460,7 @@ static int request_will_proxy(REQUEST *request) home = home_server_ldb(realmname, pool, request); if (!home) { - REDEBUG2("Failed to find live home server: Cancelling proxy"); + REDEBUG2("Failed to find live home server for realm %s: Cancelling proxy", realmname); return -1; } @@ -3373,7 +3472,11 @@ do_home: * Once we've decided to proxy a request, we cannot send * a CoA packet. So we free up any CoA packet here. */ - if (request->coa) request_done(request->coa, FR_ACTION_COA_CANCELLED); + if (request->coa) { + RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); + request_done(request->coa, FR_ACTION_COA_CANCELLED); + request->coa = NULL; + } #endif /* @@ -3605,6 +3708,7 @@ static int request_proxy(REQUEST *request) if (request->coa) { RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); request_done(request->coa, FR_ACTION_COA_CANCELLED); + request->coa = NULL; } #endif @@ -3817,6 +3921,7 @@ static void request_ping(REQUEST *request, int action) break; case FR_ACTION_PROXY_REPLY: + default: rad_assert(request->in_proxy_hash); request->home_server->num_received_pings++; @@ -3855,9 +3960,10 @@ static void request_ping(REQUEST *request, int action) mark_home_server_alive(request, home); break; - default: + case FR_ACTION_RUN: + case FR_ACTION_DUP: RDEBUG3("%s: Ignoring action %s", __FUNCTION__, action_codes[action]); - break; + return; } rad_assert(!request->in_request_hash); @@ -4339,10 +4445,10 @@ static void proxy_wait_for_reply(REQUEST *request, int action) * and should be suppressed by the proxy. */ when = request->proxy->timestamp; - when.tv_sec++; + when.tv_sec += main_config.proxy_dedup_window; if (timercmp(&now, &when, <)) { - DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d proto TCP - ID: %d", + DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d - ID: %d", inet_ntop(request->proxy->dst_ipaddr.af, &request->proxy->dst_ipaddr.ipaddr, buffer, sizeof(buffer)), @@ -4543,9 +4649,9 @@ static void request_coa_originate(REQUEST *request) VERIFY_REQUEST(request); rad_assert(request->coa != NULL); - rad_assert(request->proxy == NULL); + rad_assert(request->proxy == NULL || request->proxy->dst_port == 0); rad_assert(!request->in_proxy_hash); - rad_assert(request->proxy_reply == NULL); + rad_assert(request->proxy_reply == NULL || request->proxy_reply->src_port == 0); /* * Check whether we want to originate one, or cancel one. @@ -5018,7 +5124,11 @@ static bool coa_max_time(REQUEST *request) buffer, sizeof(buffer)), request->proxy->dst_port, mrd); - request_done(request, FR_ACTION_DONE); + if (setup_post_proxy_fail(request)) { + request_queue_or_run(request, coa_no_reply); + } else { + request_done(request, FR_ACTION_DONE); + } return true; } @@ -5386,7 +5496,6 @@ static void listener_free_cb(void *ctx) talloc_free(this); } -#ifdef WITH_TCP #ifdef WITH_PROXY static int proxy_eol_cb(void *ctx, void *data) { @@ -5426,7 +5535,6 @@ static int proxy_eol_cb(void *ctx, void *data) return 0; } #endif /* WITH_PROXY */ -#endif /* WITH_TCP */ static void event_new_fd(rad_listen_t *this) { @@ -5602,6 +5710,7 @@ static void event_new_fd(rad_listen_t *this) */ this->print(this, buffer, sizeof(buffer)); ERROR("Failed adding event handler for socket %s: %s", buffer, fr_strerror()); + this->status = RAD_LISTEN_STATUS_EOL; goto listener_is_eol; } /* end of INIT */ @@ -5645,6 +5754,7 @@ static void event_new_fd(rad_listen_t *this) fr_event_fd_delete(el, 0, this->fd); this->status = RAD_LISTEN_STATUS_REMOVE_NOW; } +#endif /* WITH_TCP */ /* * The socket has had a catastrophic error. Close it. @@ -5708,7 +5818,6 @@ static void event_new_fd(rad_listen_t *this) */ this->status = RAD_LISTEN_STATUS_REMOVE_NOW; } /* socket is at EOL */ -#endif /* WITH_TCP */ if (this->dead) goto wait_some_more; @@ -6147,7 +6256,7 @@ static void check_proxy(rad_listen_t *head) if (sock->my_ipaddr.af == AF_INET) has_v4 = true; if (sock->my_ipaddr.af == AF_INET6) has_v6 = true; break; - + default: break; } diff --git a/src/main/radclient.c b/src/main/radclient.c index 49da461..ab880dd 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -60,11 +60,13 @@ static fr_ipaddr_t server_ipaddr; static int resend_count = 1; static bool done = true; static bool print_filename = false; +static bool blast_radius = false; static fr_ipaddr_t client_ipaddr; static uint16_t client_port = 0; static int sockfd; +static int last_used_id = -1; #ifdef WITH_TCP static char const *proto = NULL; @@ -95,6 +97,7 @@ static void NEVER_RETURNS usage(void) fprintf(stderr, " <command> One of auth, acct, status, coa, disconnect or auto.\n"); fprintf(stderr, " -4 Use IPv4 address of server\n"); fprintf(stderr, " -6 Use IPv6 address of server.\n"); + fprintf(stderr, " -b Mandate checks for Blast RADIUS issue (this is not set by default).\n"); fprintf(stderr, " -c <count> Send each packet 'count' times.\n"); fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n"); fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n"); @@ -416,7 +419,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) #endif request->files = files; - request->packet->id = -1; /* allocate when sending */ + request->packet->id = last_used_id; /* either requested, or allocated by the library */ request->num = num++; /* @@ -892,7 +895,7 @@ static int send_one_packet(rc_request_t *request) /* * Haven't sent the packet yet. Initialize it. */ - if (request->packet->id == -1) { + if (!request->tries || (request->packet->id == -1)) { int i; bool rcode; @@ -949,8 +952,18 @@ static int send_one_packet(rc_request_t *request) assert(request->packet->id != -1); assert(request->packet->data == NULL); - for (i = 0; i < 4; i++) { - ((uint32_t *) request->packet->vector)[i] = fr_rand(); + if (request->packet->code == PW_CODE_ACCESS_REQUEST) { + VALUE_PAIR *vp; + + if (((vp = fr_pair_find_by_num(request->packet->vps, PW_PACKET_AUTHENTICATION_VECTOR, 0, TAG_ANY)) != NULL) && + (vp->vp_length >= 16)) { + memcpy(request->packet->vector, vp->vp_octets, 16); + + } else { + for (i = 0; i < 4; i++) { + ((uint32_t *) request->packet->vector)[i] = fr_rand(); + } + } } /* @@ -1048,11 +1061,15 @@ static int send_one_packet(rc_request_t *request) REDEBUG("Failed to send packet for ID %d", request->packet->id); deallocate_id(request); request->done = true; + stats.lost++; return -1; } if (fr_log_fp) { fr_packet_header_print(fr_log_fp, request->packet, false); + + if (fr_debug_lvl > 2) rad_print_hex(request->packet); + if (fr_debug_lvl > 0) vp_printlist(fr_log_fp, request->packet->vps); } @@ -1060,6 +1077,130 @@ static int send_one_packet(rc_request_t *request) } /* + * Do Blast RADIUS checks. + * + * The request is an Access-Request, and does NOT contain Proxy-State. + * + * The reply is a raw packet, and is NOT yet decoded. + */ +static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply) +{ + uint8_t *attr, *end; + VALUE_PAIR *vp; + bool have_message_authenticator = false; + + /* + * We've received a raw packet. Nothing has (as of yet) checked + * anything in it other than the length, and that it's a + * well-formed RADIUS packet. + */ + switch (reply->data[0]) { + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + if (reply->data[1] != request->packet->id) { + ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id); + return -1; + } + break; + + default: + ERROR("Invalid reply code %d to Access-Request", reply->data[0]); + return -1; + } + + /* + * If the reply has a Message-Authenticator, then it MIGHT be fine. + */ + attr = reply->data + 20; + end = reply->data + reply->data_len; + + /* + * It should be the first attribute, so we warn if it isn't there. + * + * But it's not a fatal error. + */ + if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) { + RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.", + fr_packet_codes[reply->data[0]]); + } + + /* + * Set up for Proxy-State checks. + * + * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY); + + while (attr < end) { + /* + * Blast RADIUS work-arounds require that + * Message-Authenticator is the first attribute in the + * reply. Note that we don't check for it being the + * first attribute, but simply that it exists. + * + * That check is a balance between securing the reply + * packet from attacks, and not violating the RFCs which + * say that there is no order to attributes in the + * packet. + * + * However, no matter the status of the '-b' flag we + * still can check for the signature of the attack, and + * discard packets which are suspicious. This behavior + * protects radclient from the attack, without mandating + * new behavior on the server side. + * + * Note that we don't set the '-b' flag by default. + * radclient is intended for testing / debugging, and is + * not intended to be used as part of a secure login / + * user checking system. + */ + if (attr[0] == PW_MESSAGE_AUTHENTICATOR) { + have_message_authenticator = true; + goto next; + } + + /* + * If there are Proxy-State attributes in the reply, they must + * match EXACTLY the Proxy-State attributes in the request. + * + * Note that we don't care if there are more Proxy-States + * in the request than in the reply. The Blast RADIUS + * issue requires _adding_ Proxy-State attributes, and + * cannot work when the server _deletes_ Proxy-State + * attributes. + */ + if (attr[0] == PW_PROXY_STATE) { + if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) { + ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id); + ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent."); + return -1; + } + + vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY); + } + + next: + attr += attr[1]; + } + + /* + * If "-b" is set, then we require Message-Authenticator in the reply. + */ + if (blast_radius && !have_message_authenticator) { + ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.", + fr_packet_codes[reply->data[0]]); + return -1; + } + + /* + * The packet doesn't look like it's a Blast RADIUS attack. The + * caller will now verify the packet signature. + */ + return 0; +} + +/* * Receive one packet, maybe. */ static int recv_one_packet(int wait_time) @@ -1101,6 +1242,8 @@ static int recv_one_packet(int wait_time) return -1; /* bad packet */ } + if (fr_debug_lvl > 2) rad_print_hex(reply); + packet_p = fr_packet_list_find_byreply(pl, reply); if (!packet_p) { ERROR("Received reply to request we did not send. (id=%d socket %d)", @@ -1111,6 +1254,20 @@ static int recv_one_packet(int wait_time) request = fr_packet2myptr(rc_request_t, packet, packet_p); /* + * We want radclient to be able to send any packet, including + * imperfect ones. However, we do NOT want to be vulnerable to + * the "Blast RADIUS" issue. Instead of adding command-line + * flags to enable/disable similar flags to what the server + * sends, we just do a few more smart checks to double-check + * things. + */ + if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && + blast_radius_check(request, reply) < 0) { + rad_free(&reply); + return -1; + } + + /* * Fails the signature validation: not a real reply. * FIXME: Silently drop it and listen for another packet. */ @@ -1243,7 +1400,7 @@ int main(int argc, char **argv) exit(1); } - while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx" + while ((c = getopt(argc, argv, "46bc:d:D:f:Fhi:n:p:qr:sS:t:vx" #ifdef WITH_TCP "P:" #endif @@ -1256,6 +1413,10 @@ int main(int argc, char **argv) force_af = AF_INET6; break; + case 'b': + blast_radius = true; + break; + case 'c': if (!isdigit((uint8_t) *optarg)) usage(); @@ -1297,6 +1458,15 @@ int main(int argc, char **argv) print_filename = true; break; + case 'i': + if (!isdigit((uint8_t) *optarg)) + usage(); + last_used_id = atoi(optarg); + if ((last_used_id < 0) || (last_used_id > 255)) { + usage(); + } + break; + case 'n': persec = atoi(optarg); if (persec <= 0) usage(); @@ -1562,6 +1732,7 @@ int main(int argc, char **argv) int n = parallel; rc_request_t *next; char const *filename = NULL; + time_t wake = 0; done = true; sleep_time = -1; @@ -1569,6 +1740,15 @@ int main(int argc, char **argv) /* * Walk over the packets, sending them. */ + for (this = request_head; this != NULL; this = this->next) { + if (this->reply) continue; + + if (!this->timestamp) continue; + + if (!wake || (wake > (this->timestamp + ((int) timeout) * (retries - this->tries)))) { + wake = this->timestamp + ((int) timeout) * (retries - this->tries); + } + } for (this = request_head; this != NULL; this = next) { next = this->next; @@ -1613,6 +1793,10 @@ int main(int argc, char **argv) break; } + if (!wake || (wake > (this->timestamp + ((int) timeout) * (retries - this->tries)))) { + wake = this->timestamp + ((int) timeout) * (retries - this->tries); + } + /* * Wait a little before sending * the next packet, if told to. @@ -1664,7 +1848,18 @@ int main(int argc, char **argv) * Still have outstanding requests. */ if (fr_packet_list_num_elements(pl) > 0) { + time_t now = time(NULL); done = false; + + /* + * The last time we wake up for a packet. + * + * If we're past that time, then give up. + */ + if (wake < now) { + break; + } + } else { sleep_time = 0; } diff --git a/src/main/radsecret b/src/main/radsecret new file mode 100755 index 0000000..2a03a2e --- /dev/null +++ b/src/main/radsecret @@ -0,0 +1,7 @@ +#!/usr/bin/env perl +# +# A tool which generates strong shared secrets. +# +use Convert::Base32; +use Crypt::URandom(); +print join('-', unpack("(A4)*", lc encode_base32(Crypt::URandom::urandom(12)))), "\n"; diff --git a/src/main/radsecret.mk b/src/main/radsecret.mk new file mode 100644 index 0000000..c5f43b4 --- /dev/null +++ b/src/main/radsecret.mk @@ -0,0 +1,5 @@ +install: $(R)/$(bindir)/radsecret + +$(R)/$(bindir)/radsecret: ${top_srcdir}/src/main/radsecret + @$(ECHO) INSTALL radsecret + $(Q)${PROGRAM_INSTALL} -c -m 755 $< $@ diff --git a/src/main/radsniff.c b/src/main/radsniff.c index e0d2b65..0458d77 100644 --- a/src/main/radsniff.c +++ b/src/main/radsniff.c @@ -1804,6 +1804,15 @@ static void _unmark_link(void *request) this->in_link_tree = false; } +/** Exit the event loop after a given timeout. + * + */ +static void timeout_event(UNUSED void *ctx) +{ + fr_event_loop_exit(events, 1); +} + + #ifdef HAVE_COLLECTDC_H /** Re-open the collectd socket * @@ -1919,6 +1928,7 @@ static void NEVER_RETURNS usage(int status) fprintf(output, " -R <filter> RADIUS attribute response filter.\n"); fprintf(output, " -s <secret> RADIUS secret.\n"); fprintf(output, " -S Write PCAP data to stdout.\n"); + fprintf(output, " -t <timeout> Stop after <timeout> seconds.\n"); fprintf(output, " -v Show program version information.\n"); fprintf(output, " -w <file> Write output packets to file.\n"); fprintf(output, " -x Print more debugging information.\n"); @@ -1947,6 +1957,8 @@ int main(int argc, char *argv[]) char buffer[1024]; int opt; + unsigned int timeout = 0; + fr_event_t *timeout_ev = NULL; char const *radius_dir = RADDBDIR; char const *dict_dir = DICTDIR; @@ -1999,7 +2011,7 @@ int main(int argc, char *argv[]) /* * Get options */ - while ((opt = getopt(argc, argv, "ab:c:Cd:D:e:Ff:hi:I:l:L:mp:P:qr:R:s:Svw:xXW:T:P:N:O:")) != EOF) { + while ((opt = getopt(argc, argv, "ab:c:Cd:D:e:Ff:hi:I:l:L:mp:P:qr:R:s:St:vw:xXW:T:P:N:O:")) != EOF) { switch (opt) { case 'a': { @@ -2120,6 +2132,10 @@ int main(int argc, char *argv[]) conf->radius_secret = optarg; break; + case 't': + timeout = atoi(optarg); + break; + case 'S': conf->to_stdout = true; break; @@ -2569,7 +2585,7 @@ int main(int argc, char *argv[]) * Setup and enter the main event loop. Who needs libev when you can roll your own... */ { - struct timeval now; + struct timeval now, when; rs_update_t update; char *buff; @@ -2632,17 +2648,26 @@ int main(int argc, char *argv[]) update.stats = &stats; update.in = in; - now.tv_sec += conf->stats.interval; - now.tv_usec = 0; - if (!fr_event_insert(events, rs_stats_process, (void *) &update, &now, &event)) { + when = now; + when.tv_sec += conf->stats.interval; + when.tv_usec = 0; + if (!fr_event_insert(events, rs_stats_process, (void *) &update, &when, &event)) { ERROR("Failed inserting stats event"); } INFO("Muting stats for the next %i milliseconds (warmup)", conf->stats.timeout); - rs_tv_add_ms(&now, conf->stats.timeout, &stats.quiet); + rs_tv_add_ms(&when, conf->stats.timeout, &stats.quiet); } - } + if (timeout) { + when = now; + when.tv_sec += timeout; + + if (!fr_event_insert(events, timeout_event, NULL, &when, &timeout_ev)) { + ERROR("Failed inserting timeout event"); + } + } + } /* * Do this as late as possible so we can return an error code if something went wrong. diff --git a/src/main/realms.c b/src/main/realms.c index 2959d82..fa42813 100644 --- a/src/main/realms.c +++ b/src/main/realms.c @@ -452,6 +452,12 @@ static CONF_PARSER limit_config[] = { { "max_requests", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.max_requests), "0" }, { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.lifetime), "0" }, { "idle_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.idle_timeout), "0" }, +#ifdef SO_RCVTIMEO + { "read_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.read_timeout), NULL }, +#endif +#ifdef SO_SNDTIMEO + { "write_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, home_server_t, limit.write_timeout), NULL }, +#endif CONF_PARSER_TERMINATOR }; @@ -475,8 +481,11 @@ static CONF_PARSER home_server_recv_coa[] = { #endif +static const char *require_message_authenticator = NULL; + static CONF_PARSER home_server_config[] = { { "nonblock", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, home_server_t, nonblock), "no" }, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, { "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL }, { "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL }, { "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL }, @@ -780,6 +789,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE home->cs = cs; home->state = HOME_STATE_UNKNOWN; home->proto = IPPROTO_UDP; + home->require_ma = main_config.require_ma; + + require_message_authenticator = false; /* * Parse the configuration into the home server @@ -787,6 +799,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE */ if (cf_section_parse(cs, home, home_server_config) < 0) goto error; + if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) { + goto error; + } + /* * It has an IP address, it must be a remote server. */ @@ -1116,11 +1132,18 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE if (tls) { int rcode; + /* + * We don't require this for TLS connections. + */ + home->require_ma = false; + home->tls = tls_client_conf_parse(tls); if (!home->tls) { goto error; } + home->tls->name = "RADIUS/TLS"; + /* * Connection timeouts for outgoing TLS connections. */ @@ -3181,7 +3204,7 @@ int home_server_afrom_file(char const *filename) goto error; } -#ifdef COA_TUNNEL +#ifdef WITH_COA_TUNNEL if (home->recv_coa) { fr_strerror_printf("Dynamic home_server '%s' cannot receive CoA requests'", p); talloc_free(home); diff --git a/src/main/state.c b/src/main/state.c index 3700062..ab7a180 100644 --- a/src/main/state.c +++ b/src/main/state.c @@ -33,13 +33,14 @@ RCSID("$Id$") #include <freeradius-devel/process.h> typedef struct state_entry_t { - uint8_t state[AUTH_VECTOR_LEN]; + uint8_t state[MD5_DIGEST_LENGTH]; time_t cleanup; struct state_entry_t *prev; struct state_entry_t *next; int tries; + bool ours; TALLOC_CTX *ctx; VALUE_PAIR *vps; @@ -379,10 +380,55 @@ static void fr_state_cleanup(state_entry_t *head) request_inject(request); } + if (entry->opaque) { + entry->free_opaque(entry->opaque); + } + + if (entry->ctx) talloc_free(entry->ctx); + talloc_free(entry); } } +static void state_entry_calc(REQUEST *request, state_entry_t *entry, VALUE_PAIR *vp) +{ + /* + * Assume our own State first. This is where the state + * is the correct size, AND we're not proxying it to an + * external home server. If we are proxying it to an + * external home server, then that home server creates + * the State attribute, and we don't control it. + */ + if (entry->ours || + (vp->vp_length == sizeof(entry->state) && + (!request->proxy || (request->proxy->dst_port == 0)))) { + memcpy(entry->state, vp->vp_octets, sizeof(entry->state)); + entry->ours = true; + + } else { + FR_MD5_CTX ctx; + + /* + * We don't control the external State attribute. + * As a result, different home servers _may_ + * create the same State attribute. In order to + * differentiate them, we "mix in" the User-Name, + * which should contain the realm. And we then + * hope that different home servers in the same + * realm don't create overlapping State + * attributes. + */ + fr_md5_init(&ctx); + fr_md5_update(&ctx, vp->vp_octets, vp->vp_length); + + vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); + if (vp) fr_md5_update(&ctx, vp->vp_octets, vp->vp_length); + + fr_md5_final(entry->state, &ctx); + fr_md5_destroy(&ctx); + } +} + /* * Create a new entry. Called with the mutex held. @@ -430,17 +476,18 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, */ if (old) { entry->tries = old->tries + 1; + entry->ours = old->ours; /* * Track State */ - if (!vp) { + if (!vp && entry->ours) { memcpy(entry->state, old->state, sizeof(entry->state)); entry->state[1] = entry->state[0] ^ entry->tries; - entry->state[8] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff); + entry->state[8] = entry->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff); entry->state[10] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff); - entry->state[12] = entry->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff); + entry->state[12] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff); } /* @@ -457,6 +504,8 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, x = fr_rand(); memcpy(entry->state + (i * 4), &x, sizeof(x)); } + + entry->ours = true; /* we created it */ } /* @@ -464,27 +513,8 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, * one we created above. */ if (vp) { - /* - * Assume our own State first. - */ - if (vp->vp_length == sizeof(entry->state)) { - memcpy(entry->state, vp->vp_octets, sizeof(entry->state)); + state_entry_calc(request, entry, vp); - /* - * Too big? Get the MD5 hash, in order - * to depend on the entire contents of State. - */ - } else if (vp->vp_length > sizeof(entry->state)) { - fr_md5_calc(entry->state, vp->vp_octets, vp->vp_length); - - /* - * Too small? Use the whole thing, and - * set the rest of entry->state to zero. - */ - } else { - memcpy(entry->state, vp->vp_octets, vp->vp_length); - memset(&entry->state[vp->vp_length], 0, sizeof(entry->state) - vp->vp_length); - } } else { vp = fr_pair_afrom_num(packet, PW_STATE, 0); fr_pair_value_memcpy(vp, entry->state, sizeof(entry->state)); @@ -497,7 +527,7 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, /* * Make unique for different virtual servers handling same request */ - *((uint32_t *)(&entry->state[4])) ^= fr_hash_string(request->server); + if (entry->ours) *((uint32_t *)(&entry->state[4])) ^= fr_hash_string(request->server); /* * Copy server to state in case it's needed for cleanup @@ -520,7 +550,7 @@ static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, /* * Find the entry, based on the State attribute. */ -static state_entry_t *fr_state_find(fr_state_t *state, const char *server, RADIUS_PACKET *packet) +static state_entry_t *fr_state_find(REQUEST *request, fr_state_t *state, const char *server, RADIUS_PACKET *packet) { VALUE_PAIR *vp; state_entry_t *entry, my_entry; @@ -528,31 +558,12 @@ static state_entry_t *fr_state_find(fr_state_t *state, const char *server, RADIU vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY); if (!vp) return NULL; - /* - * Assume our own State first. - */ - if (vp->vp_length == sizeof(my_entry.state)) { - memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state)); - - /* - * Too big? Get the MD5 hash, in order - * to depend on the entire contents of State. - */ - } else if (vp->vp_length > sizeof(my_entry.state)) { - fr_md5_calc(my_entry.state, vp->vp_octets, vp->vp_length); - - /* - * Too small? Use the whole thing, and - * set the rest of my_entry.state to zero. - */ - } else { - memcpy(my_entry.state, vp->vp_octets, vp->vp_length); - memset(&my_entry.state[vp->vp_length], 0, sizeof(my_entry.state) - vp->vp_length); - } + my_entry.ours = false; + state_entry_calc(request, &my_entry, vp); /* Make unique for different virtual servers handling same request */ - if (server) *((uint32_t *)(&my_entry.state[4])) ^= fr_hash_string(server); + if (server && my_entry.ours) *((uint32_t *)(&my_entry.state[4])) ^= fr_hash_string(server); entry = rbtree_finddata(state->tree, &my_entry); @@ -576,7 +587,7 @@ void fr_state_discard(REQUEST *request, RADIUS_PACKET *original) request->state = NULL; PTHREAD_MUTEX_LOCK(&state->mutex); - entry = fr_state_find(state, request->server, original); + entry = fr_state_find(request, state, request->server, original); if (entry) state_entry_free(state, entry); PTHREAD_MUTEX_UNLOCK(&state->mutex); } @@ -601,7 +612,7 @@ void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet) rad_assert(request->state == NULL); PTHREAD_MUTEX_LOCK(&state->mutex); - entry = fr_state_find(state, request->server, packet); + entry = fr_state_find(request, state, request->server, packet); /* * This has to be done in a mutex lock, because talloc @@ -683,7 +694,7 @@ bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET * cleanup_list = fr_state_cleanup_find(state); if (original) { - old = fr_state_find(state, request->server, original); + old = fr_state_find(request, state, request->server, original); } else { old = NULL; } diff --git a/src/main/stats.c b/src/main/stats.c index a5c672e..29f2c48 100644 --- a/src/main/stats.c +++ b/src/main/stats.c @@ -91,51 +91,57 @@ static void stats_time(fr_stats_t *stats, struct timeval *start, void request_stats_final(REQUEST *request) { rad_listen_t *listener; + RADCLIENT *client; - if (request->master_state == REQUEST_COUNTED) return; + if ((request->options & RAD_REQUEST_OPTION_STATS) != 0) return; - if (!request->listener) return; - if (!request->client) return; + /* don't count statistic requests */ + if (request->packet->code == PW_CODE_STATUS_SERVER) { + return; + } - if ((request->listener->type != RAD_LISTEN_NONE) && + listener = request->listener; + if (listener) switch (listener->type) { + case RAD_LISTEN_NONE: #ifdef WITH_ACCOUNTING - (request->listener->type != RAD_LISTEN_ACCT) && + case RAD_LISTEN_ACCT: #endif #ifdef WITH_COA - (request->listener->type != RAD_LISTEN_COA) && + case RAD_LISTEN_COA: #endif - (request->listener->type != RAD_LISTEN_AUTH)) return; + case RAD_LISTEN_AUTH: + break; - /* don't count statistic requests */ - if (request->packet->code == PW_CODE_STATUS_SERVER) - return; + default: + return; + } /* * Deal with TCP / TLS issues. The statistics are kept in the parent socket. */ - listener = request->listener; - if (listener->parent) listener = listener->parent; + if (listener && listener->parent) listener = listener->parent; + client = request->client; #undef INC_AUTH -#define INC_AUTH(_x) radius_auth_stats._x++;listener->stats._x++;request->client->auth._x++; +#define INC_AUTH(_x) radius_auth_stats._x++;if (listener) listener->stats._x++;if (client) client->auth._x++; #undef INC_ACCT #ifdef WITH_ACCOUNTING -#define INC_ACCT(_x) radius_acct_stats._x++;listener->stats._x++;request->client->acct._x++ +#define INC_ACCT(_x) radius_acct_stats._x++;if (listener) listener->stats._x++;if (client) client->acct._x++ #else #define INC_ACCT(_x) #endif #undef INC_COA #ifdef WITH_COA -#define INC_COA(_x) radius_coa_stats._x++;listener->stats._x++;request->client->coa._x++ +#define INC_COA(_x) radius_coa_stats._x++;if (listener) listener->stats._x++;if (client) client->coa._x++ #else #define INC_COA(_x) #endif #undef INC_DSC #ifdef WITH_DSC -#define INC_DSC(_x) radius_dsc_stats._x++;listener->stats._x++;request->client->dsc._x++ +#define INC_DSC(_x) radius_dsc_stats._x++;if (listener) listener->stats._x++;if (client) client->dsc._x++ #else #define INC_DSC(_x) #endif @@ -148,7 +154,7 @@ void request_stats_final(REQUEST *request) * deleted, because only the main server thread calls * this function, which makes it thread-safe. */ - if (request->reply && (request->packet->code != PW_CODE_STATUS_SERVER)) switch (request->reply->code) { + if (request->reply) switch (request->reply->code) { case PW_CODE_ACCESS_ACCEPT: INC_AUTH(total_access_accepts); @@ -247,25 +253,21 @@ void request_stats_final(REQUEST *request) switch (request->proxy->code) { case PW_CODE_ACCESS_REQUEST: proxy_auth_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: proxy_acct_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; #endif #ifdef WITH_COA case PW_CODE_COA_REQUEST: proxy_coa_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; case PW_CODE_DISCONNECT_REQUEST: proxy_dsc_stats.total_requests += request->num_proxied_requests; - request->home_server->stats.total_requests += request->num_proxied_requests; break; #endif @@ -276,7 +278,7 @@ void request_stats_final(REQUEST *request) if (!request->proxy_reply) goto done; /* simplifies formatting */ #undef INC -#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses; request->home_server->stats._x += request->num_proxied_responses; +#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses;request->home_server->stats._x += request->num_proxied_responses; switch (request->proxy_reply->code) { case PW_CODE_ACCESS_ACCEPT: @@ -347,10 +349,7 @@ void request_stats_final(REQUEST *request) done: #endif /* WITH_PROXY */ - if (request->max_time) { - RADCLIENT *client = request->client; - switch (request->packet->code) { case PW_CODE_ACCESS_REQUEST: FR_STATS_INC(auth, unresponsive_child); @@ -376,7 +375,7 @@ void request_stats_final(REQUEST *request) } } - request->master_state = REQUEST_COUNTED; + request->options |= RAD_REQUEST_OPTION_STATS; } typedef struct fr_stats2vp { @@ -525,8 +524,8 @@ void request_stats_reply(REQUEST *request) /* * Authentication. */ - if (((flag->vp_integer & 0x01) != 0) && - ((flag->vp_integer & 0xc0) == 0)) { + if (((flag->vp_integer & 0x01) != 0) && /* auth */ + ((flag->vp_integer & 0xe0) == 0)) { /* not client, server or home-server */ request_stats_addvp(request, authvp, &radius_auth_stats); } @@ -534,8 +533,8 @@ void request_stats_reply(REQUEST *request) /* * Accounting */ - if (((flag->vp_integer & 0x02) != 0) && - ((flag->vp_integer & 0xc0) == 0)) { + if (((flag->vp_integer & 0x02) != 0) && /* accounting */ + ((flag->vp_integer & 0xe0) == 0)) { /* not client, server or home-server */ request_stats_addvp(request, acctvp, &radius_acct_stats); } #endif @@ -544,8 +543,8 @@ void request_stats_reply(REQUEST *request) /* * Proxied authentication requests. */ - if (((flag->vp_integer & 0x04) != 0) && - ((flag->vp_integer & 0x20) == 0)) { + if (((flag->vp_integer & 0x04) != 0) && /* proxy-auth */ + ((flag->vp_integer & 0x20) == 0)) { /* not client */ request_stats_addvp(request, proxy_authvp, &proxy_auth_stats); } @@ -553,8 +552,8 @@ void request_stats_reply(REQUEST *request) /* * Proxied accounting requests. */ - if (((flag->vp_integer & 0x08) != 0) && - ((flag->vp_integer & 0x20) == 0)) { + if (((flag->vp_integer & 0x08) != 0) && /* proxy-accounting */ + ((flag->vp_integer & 0x20) == 0)) { /* not client */ request_stats_addvp(request, proxy_acctvp, &proxy_acct_stats); } #endif @@ -563,7 +562,7 @@ void request_stats_reply(REQUEST *request) /* * Internal server statistics */ - if ((flag->vp_integer & 0x10) != 0) { + if ((flag->vp_integer & 0x10) != 0) { /* internal */ vp = radius_pair_create(request->reply, &request->reply->vps, PW_FREERADIUS_STATS_START_TIME, VENDORPEC_FREERADIUS); if (vp) vp->vp_date = start_time.tv_sec; @@ -607,7 +606,7 @@ void request_stats_reply(REQUEST *request) /* * For a particular client. */ - if ((flag->vp_integer & 0x20) != 0) { + if ((flag->vp_integer & 0x20) != 0) { /* client */ fr_ipaddr_t ipaddr; VALUE_PAIR *server_ip, *server_port = NULL; RADCLIENT *client = NULL; @@ -764,8 +763,8 @@ void request_stats_reply(REQUEST *request) /* * For a particular "listen" socket. */ - if (((flag->vp_integer & 0x40) != 0) && - ((flag->vp_integer & 0x03) != 0)) { + if (((flag->vp_integer & 0x40) != 0) && /* server */ + ((flag->vp_integer & 0x03) != 0)) { /* auth or accounting */ rad_listen_t *this; VALUE_PAIR *server_ip, *server_port; fr_ipaddr_t ipaddr; @@ -807,7 +806,7 @@ void request_stats_reply(REQUEST *request) fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, server_port)); - if ((flag->vp_integer & 0x01) != 0) { + if ((flag->vp_integer & 0x01) != 0) { /* auth */ if ((request->listener->type == RAD_LISTEN_AUTH) || (request->listener->type == RAD_LISTEN_NONE)) { request_stats_addvp(request, authvp, &this->stats); @@ -817,7 +816,7 @@ void request_stats_reply(REQUEST *request) } #ifdef WITH_ACCOUNTING - if ((flag->vp_integer & 0x02) != 0) { + if ((flag->vp_integer & 0x02) != 0) { /* accounting */ if ((request->listener->type == RAD_LISTEN_ACCT) || (request->listener->type == RAD_LISTEN_NONE)) { request_stats_addvp(request, acctvp, &this->stats); @@ -832,8 +831,8 @@ void request_stats_reply(REQUEST *request) /* * Home servers. */ - if (((flag->vp_integer & 0x80) != 0) && - ((flag->vp_integer & 0x03) != 0)) { + if (((flag->vp_integer & 0x80) != 0) && /* home-server */ + ((flag->vp_integer & 0x03) != 0)) { /* auth or accounting */ home_server_t *home; VALUE_PAIR *server_ip, *server_port; fr_ipaddr_t ipaddr; @@ -935,7 +934,7 @@ void request_stats_reply(REQUEST *request) PW_FREERADIUS_STATS_LAST_PACKET_SENT, VENDORPEC_FREERADIUS); if (vp) vp->vp_date = home->last_packet_sent; - if ((flag->vp_integer & 0x01) != 0) { + if ((flag->vp_integer & 0x01) != 0) { /* auth */ if (home->type == HOME_TYPE_AUTH) { request_stats_addvp(request, proxy_authvp, &home->stats); @@ -945,7 +944,7 @@ void request_stats_reply(REQUEST *request) } #ifdef WITH_ACCOUNTING - if ((flag->vp_integer & 0x02) != 0) { + if ((flag->vp_integer & 0x02) != 0) { /* accounting */ if (home->type == HOME_TYPE_ACCT) { request_stats_addvp(request, proxy_acctvp, &home->stats); @@ -991,14 +990,14 @@ void radius_stats_ema(fr_stats_ema_t *ema, } - tdiff = start->tv_sec; - tdiff -= end->tv_sec; + tdiff = end->tv_sec; + tdiff -= start->tv_sec; micro = (int) tdiff; if (micro > 40) micro = 40; /* don't overflow 32-bit ints */ micro *= USEC; - micro += start->tv_usec; - micro -= end->tv_usec; + micro += end->tv_usec; + micro -= start->tv_usec; micro *= EMA_SCALE; diff --git a/src/main/threads.c b/src/main/threads.c index a187106..5730b5e 100644 --- a/src/main/threads.c +++ b/src/main/threads.c @@ -291,7 +291,7 @@ static void tls_mutexes_destroy(void) #ifdef HAVE_CRYPTO_SET_LOCKING_CALLBACK int i, num; - rad_assert(ssl_mutex != NULL); + rad_assert(ssl_mutexes != NULL); num = CRYPTO_num_locks(); diff --git a/src/main/tls.c b/src/main/tls.c index c8cae3b..736ee41 100644 --- a/src/main/tls.c +++ b/src/main/tls.c @@ -404,7 +404,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, * The passed identity is weird. Deny it. */ if (!identity_is_safe(identity)) { - RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity); + RWDEBUG("(TLS) %s - Invalid characters in PSK identity %s", conf->name, identity); return 0; } @@ -421,7 +421,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query, NULL, NULL); if (!hex_len) { - RWDEBUG("(TLS) PSK expansion returned an empty string."); + RWDEBUG("(TLS) %s - PSK expansion returned an empty string.", conf->name); return 0; } @@ -431,7 +431,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, * the truncation, and complain about it. */ if (hex_len > (2 * max_psk_len)) { - RWDEBUG("(TLS) Returned PSK is too long (%u > %u)", + RWDEBUG("(TLS) %s - Returned PSK is too long (%u > %u)", conf->name, (unsigned int) hex_len, 2 * max_psk_len); return 0; } @@ -635,9 +635,11 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con case SSL_ERROR_WANT_READ: ssn->connected = false; + RDEBUG("(TLS) %s - tls_new_client_session WANT_READ", conf->name); return ssn; case SSL_ERROR_WANT_WRITE: + RDEBUG("(TLS) %s - tls_new_client_session WANT_WRITE", conf->name); ssn->connected = false; return ssn; } @@ -681,7 +683,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU rad_assert(request != NULL); - RDEBUG2("(TLS) Initiating new session"); + RDEBUG2("(TLS) %s -Initiating new session", conf->name); /* * Replace X509 store if it is time to update CRLs/certs in ca_path @@ -690,10 +692,10 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU pthread_mutex_lock(&conf->mutex); /* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */ if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { - RDEBUG2("Flushing X509 store to re-read data from ca_path dir"); + RDEBUG2("(TLS) Flushing X509 store to re-read data from ca_path dir"); if ((new_cert_store = fr_init_x509_store(conf)) == NULL) { - RERROR("(TLS) Error replacing X509 store, out of memory (?)"); + RERROR("(TLS) %s - Error replacing X509 store, out of memory (?)", conf->name); } else { if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store); /* @@ -752,7 +754,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU SSL_set_app_data(new_tls, NULL); if ((state = talloc_zero(ctx, tls_session_t)) == NULL) { - RERROR("(TLS) Error allocating memory for SSL state"); + RERROR("(TLS) %s - Error allocating memory for SSL state", conf->name); return NULL; } session_init(state); @@ -808,7 +810,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY); if (!key) key = vp; - RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue); + RDEBUG2("(TLS) %s - Loading session certificate file \"%s\"", conf->name, vp->vp_strvalue); if (conf->realms) { fr_realm_ctx_t my_r, *r; @@ -887,11 +889,18 @@ after_chain: * Verify the peer certificate, if asked. */ if (client_cert) { - RDEBUG2("(TLS) Setting verify mode to require certificate from client"); + RDEBUG2("(TLS) %s - Setting verify mode to require certificate from client", conf->name); verify_mode = SSL_VERIFY_PEER; verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; verify_mode |= SSL_VERIFY_CLIENT_ONCE; } +#ifdef PSK_MAX_IDENTITY_LEN + else if (conf->psk_identity) { + RDEBUG2("(TLS) %s - Setting verify peer mode due to PSK", conf->name); + verify_mode = SSL_VERIFY_PEER; + verify_mode |= SSL_VERIFY_CLIENT_ONCE; + } +#endif SSL_set_verify(state->ssl, verify_mode, cbtls_verify); SSL_set_ex_data(state->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); @@ -970,14 +979,14 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) int err; if (ssn->invalid_hb_used) { - REDEBUG("(TLS) OpenSSL Heartbeat attack detected. Closing connection"); + REDEBUG("(TLS) %s - OpenSSL Heartbeat attack detected. Closing connection", ssn->conf->name); return 0; } if (ssn->dirty_in.used > 0) { err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used); if (err != (int) ssn->dirty_in.used) { - REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); + REDEBUG("(TLS) %s - Failed writing %zd bytes to SSL BIO: %d", ssn->conf->name, ssn->dirty_in.used, err); record_init(&ssn->dirty_in); return 0; } @@ -998,7 +1007,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) VALUE_PAIR *vp; char const *str_version; - RDEBUG2("(TLS) Connection Established"); + RDEBUG2("(TLS) %s - Connection Established", ssn->conf->name); ssn->is_init_finished = true; vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0); @@ -1049,10 +1058,10 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) REXDENT(); } } - else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); } - else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); } - else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); } - else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); } + else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) %s - In Handshake Phase", ssn->conf->name); } + else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) %s - Before Handshake Phase", ssn->conf->name); } + else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) %s- In Accept mode", ssn->conf->name); } + else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) %s - In Connect mode", ssn->conf->name); } #if OPENSSL_VERSION_NUMBER >= 0x10001000L /* @@ -1070,7 +1079,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) * to get the session is a hard fail. */ if (!ssn->ssl_session && ssn->is_init_finished) { - RDEBUG("(TLS) Failed getting session"); + RDEBUG("(TLS) %s - Failed getting session", ssn->conf->name); return 0; } } @@ -1084,12 +1093,12 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, sizeof(ssn->dirty_out.data)); if (err > 0) { - RDEBUG3("(TLS) got %d bytes of data", err); + RDEBUG3("(TLS) %s- got %d bytes of data", ssn->conf->name, err); ssn->dirty_out.used = err; } else if (BIO_should_retry(ssn->from_ssl)) { record_init(&ssn->dirty_in); - RDEBUG2("(TLS) Asking for more data in tunnel."); + RDEBUG2("(TLS) %s - Asking for more data in tunnel.", ssn->conf->name); return 1; } else { @@ -1098,7 +1107,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) return 0; } } else { - RDEBUG2("(TLS) Application data."); + RDEBUG2("(TLS) %s - Application data.", ssn->conf->name); /* Its clean application data, leave whatever is in the buffer */ #if 0 record_init(&ssn->clean_out); @@ -1245,6 +1254,7 @@ void tls_session_information(tls_session_t *tls_session) REQUEST *request; VALUE_PAIR *vp; char content_type[16], alert_buf[16]; + char name_buf[128]; char buffer[32]; /* @@ -1262,7 +1272,12 @@ void tls_session_information(tls_session_t *tls_session) request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); if (!request) return; - str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv"; + if (tls_session->info.origin) { + snprintf(name_buf, sizeof(name_buf), "(TLS) %s - send", tls_session->conf->name); + } else { + snprintf(name_buf, sizeof(name_buf), "(TLS) %s - recv", tls_session->conf->name); + } + str_write_p = name_buf; #define FROM_CLIENT (tls_session->info.origin == 0) @@ -1605,7 +1620,7 @@ void tls_session_information(tls_session_t *tls_session) RDEBUG2("%s", tls_session->info.info_description); - if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details); + if (FROM_CLIENT && details) RDEBUG2("(TLS) %s - The client is informing us that %s.", tls_session->conf->name, details); } static CONF_PARSER cache_config[] = { @@ -1735,6 +1750,10 @@ static CONF_PARSER tls_client_config[] = { { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, fr_tls_server_conf_t, certificate_file), NULL }, { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, fr_tls_server_conf_t, ca_file), NULL }, { "private_key_password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, fr_tls_server_conf_t, private_key_password), NULL }, +#ifdef PSK_MAX_IDENTITY_LEN + { "psk_identity", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, psk_identity), NULL }, + { "psk_hexphrase", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, fr_tls_server_conf_t, psk_password), NULL }, +#endif { "dh_file", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, dh_file), NULL }, { "random_file", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, random_file), NULL }, { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, fragment_size), "1024" }, @@ -1924,7 +1943,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) blob_len = i2d_SSL_SESSION(sess, NULL); if (blob_len < 1) { /* something went wrong */ - if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length"); + if (request) RWDEBUG("(TLS) %s - Session serialisation failed, could not determine required buffer length", conf->name); return 0; } @@ -1932,14 +1951,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) /* alloc and convert to ASN.1 */ sess_blob = malloc(blob_len); if (!sess_blob) { - RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); + RWDEBUG("(TLS) %s - Session serialisation failed, couldn't allocate buffer (%d bytes)", conf->name, blob_len); return 0; } /* openssl mutates &p */ p = sess_blob; rv = i2d_SSL_SESSION(sess, &p); if (rv != blob_len) { - if (request) RWDEBUG("(TLS) Session serialisation failed"); + if (request) RWDEBUG("(TLS) %s - Session serialisation failed", conf->name); goto error; } @@ -1948,8 +1967,8 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) conf->session_cache_path, FR_DIR_SEP, buffer); fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR); if (fd < 0) { - if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s", - filename, fr_syserror(errno)); + if (request) RERROR("(TLS) %s - Session serialisation failed, failed opening session file %s: %s", + conf->name, filename, fr_syserror(errno)); goto error; } @@ -1971,7 +1990,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) while (todo > 0) { rv = write(fd, p, todo); if (rv < 1) { - if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno)); + if (request) RWDEBUG("(TLS) %s - Failed writing session: %s", conf->name, fr_syserror(errno)); close(fd); goto error; } @@ -1979,7 +1998,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) todo -= rv; } close(fd); - if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); + if (request) RWDEBUG("(TLS) %s - Wrote session %s to %s (%d bytes)", conf->name, buffer, filename, blob_len); } error: @@ -2102,20 +2121,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer); fd = open(filename, O_RDONLY); if (fd < 0) { - RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno)); + RWDEBUG("(TLS) %s - No persisted session file %s: %s", conf->name, filename, fr_syserror(errno)); goto error; } rv = fstat(fd, &st); if (rv < 0) { - RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); + RWDEBUG("(TLS) %s - Failed stating persisted session file %s: %s", conf->name, filename, fr_syserror(errno)); close(fd); goto error; } sess_data = talloc_array(NULL, unsigned char, st.st_size); if (!sess_data) { - RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); + RWDEBUG("(TLS) %s- Failed allocating buffer for persisted session (%d bytes)", conf->name, (int) st.st_size); close(fd); goto error; } @@ -2125,7 +2144,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l while (todo > 0) { rv = read(fd, q, todo); if (rv < 1) { - RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno)); + RWDEBUG("(TLS) %s - Failed reading persisted session: %s", conf->name, fr_syserror(errno)); close(fd); goto error; } @@ -2149,7 +2168,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l memcpy(&o, &p, sizeof(o)); sess = d2i_SSL_SESSION(NULL, o, st.st_size); if (!sess) { - RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + RWDEBUG("(TLS) %s - Failed loading persisted session: %s", conf->name, ERR_error_string(ERR_get_error(), NULL)); goto error; } @@ -2159,7 +2178,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l rv = pairlist_read(talloc_ctx, filename, &pairlist, 1); if (rv < 0) { /* not safe to un-persist a session w/o VPs */ - RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer); + RWDEBUG("(TLS) %s - Failed loading persisted VPs for session %s", conf->name, buffer); SSL_SESSION_free(sess); sess = NULL; goto error; @@ -2173,7 +2192,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l time_t expires; if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) { - RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror()); + RDEBUG2("(TLS) %s - Failed getting certificate expiration, removing cache entry for session %s - %s", conf->name, buffer, fr_strerror()); SSL_SESSION_free(sess); sess = NULL; goto error; @@ -2193,8 +2212,8 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l if (vp) { if ((request->timestamp + vp->vp_integer) > expires) { vp->vp_integer = expires - request->timestamp; - RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", - vp->vp_integer); + RWDEBUG2("(TLS) %s - Updating Session-Timeout to %u, due to impending certificate expiration", + conf->name, vp->vp_integer); } } } @@ -2208,8 +2227,8 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); if (type && (type->vp_integer != vp->vp_integer)) { - REDEBUG("Resumption has changed EAP types for session %s", buffer); - REDEBUG("Rejecting session due to protocol violations"); + REDEBUG("(TLS) %s - Resumption has changed EAP types for session %s", conf->name, buffer); + REDEBUG("(TLS) %s - Rejecting session due to protocol violations", conf->name); goto error; } } @@ -2524,8 +2543,8 @@ static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int le if (vp) { if ((request->timestamp + vp->vp_integer) > expires) { vp->vp_integer = expires - request->timestamp; - RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", - vp->vp_integer); + RWDEBUG2("(TLS) %s - Updating Session-Timeout to %u, due to impending certificate expiration", + conf->name, vp->vp_integer); } } } @@ -2535,7 +2554,7 @@ static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int le */ vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY); if (!vp) { - RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer); + RWDEBUG("(TLS) %s - Failed to find TLS-Session-Data in 'session-state' list for session %s", conf->name, buffer); goto error; } @@ -2552,7 +2571,7 @@ static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int le p = vp->vp_octets; sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length); if (!sess) { - RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + RWDEBUG("(TLS) %s - Failed loading persisted session: %s", conf->name, ERR_error_string(ERR_get_error(), NULL)); goto error; } @@ -2629,7 +2648,7 @@ typedef enum { } ocsp_status_t; static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issuer_cert, X509 *client_cert, - fr_tls_server_conf_t *conf) + STACK_OF(X509) *untrusted, fr_tls_server_conf_t *conf) { OCSP_CERTID *certid; OCSP_REQUEST *req; @@ -2810,7 +2829,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue REDEBUG("ocsp: Response has wrong nonce value"); goto ocsp_end; } - if (OCSP_basic_verify(bresp, NULL, store, 0)!=1){ + if (OCSP_basic_verify(bresp, untrusted, store, 0)!=1){ REDEBUG("ocsp: Couldn't verify OCSP basic response"); goto ocsp_end; } @@ -2931,10 +2950,6 @@ static char const *cert_attr_names[9][2] = { #define FR_TLS_SAN_UPN (7) #define FR_TLS_VALID_SINCE (8) -static const char *cert_names[2] = { - "client", "server", -}; - /* * Before trusting a certificate, you must make sure that the * certificate is 'valid'. There are several steps that your @@ -3048,7 +3063,7 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) buf[0] = '\0'; sn = X509_get_serialNumber(client_cert); - RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup ]); + RDEBUG2("(TLS) %s - Creating attributes from %d certificate in chain", conf->name, lookup + 1); RINDENT(); /* @@ -3340,8 +3355,8 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) #if 0 ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert)); -#endif break; +#endif } /* @@ -3366,8 +3381,10 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) if (conf->disallow_untrusted || RDEBUG_ENABLED2) { int i; - WARN("Certificate chain - %i cert(s) untrusted", + WARN("Certificate chain - %i intermediate CA cert(s) untrusted", X509_STORE_CTX_get_num_untrusted(ctx)); + WARN("To forbid these certificates see 'reject_unknown_intermediate_ca'"); + for (i = sk_X509_num(untrusted); i > 0 ; i--) { X509 *this_cert = sk_X509_value(untrusted, i - 1); @@ -3417,7 +3434,7 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) } } /* check_cert_cn */ -#ifdef HAVE_OPENSSL_OCSP_H +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && defined(HAVE_OPENSSL_OCSP_H) if (my_ok) { /* * No OCSP, allow external verification. @@ -3447,7 +3464,7 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) * run the external verification routine. If it's marked as * "skip verify on OK", then we don't do verify. */ - my_ok = ocsp_check(request, ocsp_store, issuer_cert, client_cert, conf); + my_ok = ocsp_check(request, ocsp_store, issuer_cert, client_cert, untrusted, conf); if (my_ok != OCSP_STATUS_FAILED) { do_verify = !conf->verify_skip_if_ocsp_ok; } @@ -4161,6 +4178,16 @@ post_ca: ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); return NULL; } + +#ifdef WITH_RADIUSV11 + /* + * RADIUS 1.1 requires TLS 1.3 or later. + */ + if (conf->radiusv11 && (min_version < TLS1_3_VERSION)) { + WARN(LOG_PREFIX ": The configuration allows TLS <1.3. RADIUS/1.1 MUST use TLS 1.3"); + WARN(LOG_PREFIX ": Please set: tls_min_version = '1.3'"); + } +#endif } else { #ifdef WITH_RADIUSV11 /* diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c index fa8c382..3dc786b 100644 --- a/src/main/tls_listen.c +++ b/src/main/tls_listen.c @@ -377,7 +377,6 @@ static int tls_socket_recv(rad_listen_t *listener) REQUEST *request; listen_socket_t *sock = listener->data; fr_tls_status_t status; - RADCLIENT *client = sock->client; if (!sock->packet) { sock->packet = rad_alloc(sock, false); @@ -580,6 +579,7 @@ check_for_setup: * or any other contents. */ request->packet->code = PW_CODE_STATUS_SERVER; + request->packet->id = request->reply->id = 0; request->packet->data = talloc_zero_array(request->packet, uint8_t, 20); request->packet->data[0] = PW_CODE_STATUS_SERVER; request->packet->data[3] = 20; @@ -673,6 +673,7 @@ read_application_data: #ifdef WITH_RADIUSV11 packet->radiusv11 = sock->radiusv11; #endif + packet->tls = true; if (!rad_packet_ok(packet, 0, NULL)) { if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -708,8 +709,6 @@ read_application_data: } } - FR_STATS_INC(auth, total_requests); - return 1; } @@ -874,6 +873,7 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) */ if (sock->state == LISTEN_TLS_CHECKING) { if (request->reply->code != PW_CODE_ACCESS_ACCEPT) { + RDEBUG("(TLS) Connection checks failed - closing connection"); listener->status = RAD_LISTEN_STATUS_EOL; listener->tls = NULL; /* parent owns this! */ @@ -887,6 +887,7 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) /* * Resume reading from the listener. */ + RDEBUG("(TLS) Connection checks succeeded - continuing with normal reads"); listener->status = RAD_LISTEN_STATUS_RESUME; radius_update_listener(listener); @@ -1286,6 +1287,7 @@ int proxy_tls_recv(rad_listen_t *listener) } #endif + packet->tls = true; /* * FIXME: Client MIB updates? @@ -1373,6 +1375,7 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) * if there's no packet, encode it here. */ if (!request->proxy->data) { + request->reply->tls = true; request->proxy_listener->proxy_encode(request->proxy_listener, request); } @@ -1406,9 +1409,11 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) return -1; } + RDEBUG3("(TLS) has %zu bytes in the buffer", sock->ssn->clean_out.used); + memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); sock->ssn->clean_out.used += request->proxy->data_len; - RDEBUG3("(TLS) Writing %zu bytes for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); + RDEBUG3("(TLS) Saving %zu bytes of RADIUS traffic for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; @@ -1508,6 +1513,8 @@ int proxy_tls_send_reply(rad_listen_t *listener, REQUEST *request) if ((listener->status != RAD_LISTEN_STATUS_INIT && (listener->status != RAD_LISTEN_STATUS_KNOWN))) return 0; + request->reply->tls = true; + /* * Pack the VPs */ diff --git a/src/main/tmpl.c b/src/main/tmpl.c index 6ec2598..6746bde 100644 --- a/src/main/tmpl.c +++ b/src/main/tmpl.c @@ -579,6 +579,7 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, long num; char *q; tmpl_type_t type = TMPL_TYPE_ATTR; + DICT_ATTR const *da; value_pair_tmpl_attr_t attr; /* So we don't fill the tmpl with junk and then error out */ @@ -694,6 +695,16 @@ ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name, } /* + * Canonicalize the attribute. + * + * We can define multiple names for one attribute. In + * which case we only use the canonical name. + */ + da = dict_attrbyvalue(attr.da->attr, attr.da->vendor); + if (da && (attr.da != da)) attr.da = da; + + + /* * The string MIGHT have a tag. */ if (*p == ':') { diff --git a/src/main/unittest.c b/src/main/unittest.c index 72fdadc..c82d31d 100644 --- a/src/main/unittest.c +++ b/src/main/unittest.c @@ -55,7 +55,7 @@ char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING #endif ; -fr_event_list_t *el = NULL; +static fr_event_list_t *el = NULL; /* * Static functions. diff --git a/src/main/util.c b/src/main/util.c index b216cc9..607bcaa 100644 --- a/src/main/util.c +++ b/src/main/util.c @@ -398,15 +398,15 @@ size_t rad_filename_escape(UNUSED REQUEST *request, char *out, size_t outlen, ch switch (utf8_len) { case 2: - snprintf(out, freespace, "-%x-%x", in[0], in[1]); + snprintf(out, freespace, "-%x-%x", (uint8_t)in[0], (uint8_t)in[1]); break; case 3: - snprintf(out, freespace, "-%x-%x-%x", in[0], in[1], in[2]); + snprintf(out, freespace, "-%x-%x-%x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2]); break; case 4: - snprintf(out, freespace, "-%x-%x-%x-%x", in[0], in[1], in[2], in[3]); + snprintf(out, freespace, "-%x-%x-%x-%x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2], (uint8_t)in[3]); break; } diff --git a/src/main/version.c b/src/main/version.c index 2fe3428..c190337 100644 --- a/src/main/version.c +++ b/src/main/version.c @@ -613,7 +613,7 @@ void version_print(void) DEBUG2(" "); } INFO("FreeRADIUS Version " RADIUSD_VERSION_STRING); - INFO("Copyright (C) 1999-2022 The FreeRADIUS server project and contributors"); + INFO("Copyright (C) 1999-2023 The FreeRADIUS server project and contributors"); INFO("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A"); INFO("PARTICULAR PURPOSE"); INFO("You may redistribute copies of FreeRADIUS under the terms of the"); diff --git a/src/modules/proto_dhcp/dhcpclient.c b/src/modules/proto_dhcp/dhcpclient.c index 5ab4365..7feabbf 100644 --- a/src/modules/proto_dhcp/dhcpclient.c +++ b/src/modules/proto_dhcp/dhcpclient.c @@ -52,9 +52,10 @@ struct sockaddr_ll ll; /* Socket address structure */ static char *iface = NULL; static int iface_ind = -1; -# define DEBUG if (fr_debug_lvl && fr_log_fp) fr_printf_log #endif +# define DEBUG if (fr_debug_lvl && fr_log_fp) fr_printf_log + static RADIUS_PACKET *reply = NULL; static bool reply_expected = true; @@ -101,6 +102,7 @@ static void NEVER_RETURNS usage(void) #ifdef HAVE_LINUX_IF_PACKET_H fprintf(stderr, " -i <interface> Use this interface to send/receive at packet level on a raw socket.\n"); #endif + fprintf(stderr, " -r <retries> On timeout, retry sending the packet 'retries' times.\n"); fprintf(stderr, " -t <timeout> Wait 'timeout' seconds for a reply (may be a floating point number).\n"); fprintf(stderr, " -v Show program version information.\n"); fprintf(stderr, " -x Debugging mode.\n"); @@ -130,6 +132,7 @@ static RADIUS_PACKET *request_init(char const *filename) return NULL; } } else { + DEBUG("Reading packets from stdin\n"); fp = stdin; } diff --git a/src/modules/rlm_counter/rlm_counter.c b/src/modules/rlm_counter/rlm_counter.c index ff46aef..9e3c753 100644 --- a/src/modules/rlm_counter/rlm_counter.c +++ b/src/modules/rlm_counter/rlm_counter.c @@ -839,7 +839,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *reque /* * User is denied access, send back a reply message */ - sprintf(msg, "Your maximum %s usage time has been reached", inst->reset); + snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset); pair_make_reply("Reply-Message", msg, T_OP_EQ); REDEBUG("Maximum %s usage time reached", inst->reset); diff --git a/src/modules/rlm_date/rlm_date.c b/src/modules/rlm_date/rlm_date.c index 79191e7..739395c 100644 --- a/src/modules/rlm_date/rlm_date.c +++ b/src/modules/rlm_date/rlm_date.c @@ -19,9 +19,10 @@ * @brief Translates timestrings between formats. * * @author Artur Malinowski <artur@wow.com> + * @author Matthew Newton * * @copyright 2013 Artur Malinowski <artur@wow.com> - * @copyright 1999-2013 The FreeRADIUS Server Project. + * @copyright 1999-2023 The FreeRADIUS Server Project. */ #include <freeradius-devel/radiusd.h> @@ -116,6 +117,173 @@ static ssize_t xlat_date_convert(void *instance, REQUEST *request, char const *f } DIAG_ON(format-nonliteral) + +/** Get time in ms since either epoch or another value + * + * %{time_since:s} - return seconds since epoch + * %{time_since:ms 0} - return milliseconds since epoch + * %{time_since:us 1695745763034443} - return microseconds since Tue 26 Sep 17:29:23.034443 BST 2023 + * %{time_since:us &Tmp-Integer64-1} - return microseconds since value in &Tmp-Integer64-1 + */ + +static ssize_t xlat_time_since(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) +{ + uint64_t time_now = 0; + uint64_t time_delta = 0; + uint64_t time_since = 0; + struct timeval tv; + + enum timebase { + S = 1, + MS = 1000, + US = 1000000, + }; + enum timebase time_base; + + while (isspace((uint8_t) *fmt)) fmt++; + + /* + * Work out what time base we are using, s, ms or us. + */ + if (fmt[0] == 'm' && fmt[1] == 's') { + time_base = MS; + fmt += 2; + } else if (fmt[0] == 'u' && fmt[1] == 's') { + time_base = US; + fmt += 2; + } else if (fmt[0] == 's') { + time_base = S; + fmt++; + } else { + REDEBUG("Time base (ms, us, s) missing in time_since xlat"); + error: + *out = '\0'; + return -1; + } + + if (fmt[0] != '\0' && fmt[0] != ' ') { + REDEBUG("Invalid arguments passed to time_since xlat"); + goto error; + } + + while (isspace((uint8_t) *fmt)) fmt++; + + /* + * Handle the different formats that we can be passed + */ + if (fmt[0] == '\0') { + /* + * %{time_since:[mu]?s} - epoch + */ + time_since = 0; + + } else if (fmt[0] == '&') { + /* + * We were provided with an attribute + * + * %{time_since:[mu]?s &list:Attr-Name} + */ + value_data_t outnum; + VALUE_PAIR *vp; + vp_tmpl_t vpt; + ssize_t slen; + + fmt++; + slen = tmpl_from_attr_substr(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); + if (slen <= 0) { + /* Attribute name doesn't exist */ + REDEBUG("Unable to parse attribute in time_since xlat"); + goto error; + } + fmt += slen; + + if (tmpl_find_vp(&vp, request, &vpt) < 0) { + /* Attribute exists but is not in the list */ + RWDEBUG("Can't find &%.*s", (int)vpt.len, vpt.name); + goto error; + } + + if (vp->da->type == PW_TYPE_INTEGER64) { + /* + * Int64 is easy + */ + time_since = vp->vp_integer64; + } else { + /* + * ...but not others - try and convert, but warn it's likely nonsensical. + */ + if (value_data_cast(request, &outnum, + PW_TYPE_INTEGER64, NULL, vp->da->type, NULL, + &vp->data, vp->vp_length) < 0) { + REDEBUG("Unable to convert %s to integer", fmt); + goto error; + } + if (vp->da->type == PW_TYPE_DATE) { + /* + * Special case a Date - we know it's seconds + */ + RDEBUG3("Attribute \"%s\" is a date; multiplying seconds by %d", fmt, time_base); + time_since = outnum.integer64 * time_base; + } else { + RWDEBUG("Attribute \"%s\" is not integer, conversion may not make sense", fmt); + time_since = outnum.integer64; + } + } + + } else if (fmt[0] == '-') { + REDEBUG("time_since xlat only accepts positive integers"); + goto error; + + } else { + /* + * Otherwise we hope we were provided with an integer value + * + * %{time_since:[mu]?s 12345} + */ + if (sscanf(fmt, "%" PRIu64, &time_since) != 1) { + REDEBUG("Failed parsing \"%s\" as integer", fmt); + goto error; + } + } + + /* + * Get current time and add milli/micro component if needed + */ + gettimeofday(&tv, NULL); + + time_now = (uint64_t)tv.tv_sec * time_base; + + if (time_base == MS) { + time_now += (uint64_t)tv.tv_usec / 1000; + } else if (time_base == US) { + time_now += (uint64_t)tv.tv_usec; + } + + /* + * time_since needs to be in the past + */ + if (time_since > time_now) { + REDEBUG("time provided to time_since needs to be in the past"); + goto error; + } + + /* + * Calculate time since provided value + */ + time_delta = time_now - time_since; + + /* + * Write out and return + */ + if ((size_t)snprintf(out, outlen, "%" PRIu64, time_delta) >= outlen) { + REDEBUG("Insufficient space to write 64-bit time value"); + goto error; + } + + return 0; +} + + static int mod_bootstrap(CONF_SECTION *conf, void *instance) { rlm_date_t *inst = instance; @@ -126,6 +294,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) } xlat_register(inst->xlat_name, xlat_date_convert, NULL, inst); + xlat_register("time_since", xlat_time_since, NULL, inst); return 0; } diff --git a/src/modules/rlm_detail/rlm_detail.c b/src/modules/rlm_detail/rlm_detail.c index 036549f..949811c 100644 --- a/src/modules/rlm_detail/rlm_detail.c +++ b/src/modules/rlm_detail/rlm_detail.c @@ -64,6 +64,8 @@ typedef struct detail_instance { bool escape; //!< do filename escaping, yes / no + bool dates_as_integer; + xlat_escape_t escape_func; //!< escape function exfile_t *ef; //!< Log file handler @@ -79,6 +81,7 @@ static const CONF_PARSER module_config[] = { { "permissions", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_detail_t, perm), "0600" }, { "group", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_detail_t, group), NULL }, { "locking", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_detail_t, locking), "no" }, + { "dates_as_integer", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_detail_t, dates_as_integer), "no" }, { "escape_filenames", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_detail_t, escape), "no" }, { "log_packet_header", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_detail_t, log_srcdst), "no" }, CONF_PARSER_TERMINATOR @@ -317,7 +320,12 @@ static int detail_write(FILE *out, rlm_detail_t *inst, REQUEST *request, RADIUS_ */ op = vp->op; vp->op = T_OP_EQ; - vp_print(out, vp); + + if ((vp->da->type == PW_TYPE_DATE) && inst->dates_as_integer) { + WRITE("\t%s = %u\n", vp->da->name, vp->vp_date); + } else { + vp_print(out, vp); + } vp->op = op; } } @@ -336,7 +344,7 @@ static int detail_write(FILE *out, rlm_detail_t *inst, REQUEST *request, RADIUS_ } #endif } - WRITE("\tTimestamp = %ld\n", (unsigned long) request->timestamp); + WRITE("\tTimestamp = %lu\n", (unsigned long) request->timestamp); WRITE("\n"); diff --git a/src/modules/rlm_dpsk/all.mk b/src/modules/rlm_dpsk/all.mk new file mode 100644 index 0000000..8da2475 --- /dev/null +++ b/src/modules/rlm_dpsk/all.mk @@ -0,0 +1,10 @@ +TARGETNAME := rlm_dpsk + +ifneq "$(OPENSSL_LIBS)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := $(TARGETNAME).c + +SRC_CFLAGS := +TGT_LDLIBS := diff --git a/src/modules/rlm_dpsk/rlm_dpsk.c b/src/modules/rlm_dpsk/rlm_dpsk.c new file mode 100644 index 0000000..6ca43ee --- /dev/null +++ b/src/modules/rlm_dpsk/rlm_dpsk.c @@ -0,0 +1,940 @@ +/* + * Copyright (C) 2023 Network RADIUS SARL (legal@networkradius.com) + * + * This software may not be redistributed in any form without the prior + * written consent of Network RADIUS. + * + * 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. + */ + +/** + * $Id$ + * @file rlm_dpsk.c + * @brief Dynamic PSK for WiFi + * + * @copyright 2023 Network RADIUS SAS (legal@networkradius.com) + */ +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include <freeradius-devel/dlist.h> +#include <freeradius-devel/rad_assert.h> + +#include <openssl/ssl.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> + +#include <ctype.h> + +#define PW_FREERADIUS_8021X_ANONCE (1) +#define PW_FREERADIUS_8021X_EAPOL_KEY_MSG (2) + +#define VENDORPEC_FREERADIUS_EVS5 ((((uint32_t) 245) << 24) | VENDORPEC_FREERADIUS) + +#define VENDORPEC_RUCKUS (25053) +#define PW_RUCKUS_BSSID (14) +#define PW_RUCKUS_DPSK_PARAMS (152) + +//#define PW_RUCKUS_DPSK_CIPHER (PW_RUCKUS_DPSK_PARAMS | (2 << 8)) +#define PW_RUCKUS_DPSK_ANONCE (PW_RUCKUS_DPSK_PARAMS | (3 << 8)) +#define PW_RUCKUS_DPSK_EAPOL_KEY_FRAME (PW_RUCKUS_DPSK_PARAMS | (4 << 8)) + + +/* + Header: 02030075 + + descriptor 02 + information 010a + length 0010 + replay counter 000000000000001 + snonce c3bb319516614aacfb44e933bf1671131fb1856e5b2721952d414ce3f5aa312b + IV 0000000000000000000000000000000 + rsc 0000000000000000 + reserved 0000000000000000 + mic 35cddcedad0dfb6a12a2eca55c17c323 + data length 0016 + data 30140100000fac040100000fac040100000fac028c00 + + 30 + 14 length of data + 01 ... +*/ + +typedef struct eapol_key_frame_t { + uint8_t descriptor; // message number 2 + uint16_t information; // + uint16_t length; // always 0010, for 16 octers + uint8_t replay_counter[8]; // usually "1" + uint8_t nonce[32]; // random token + uint8_t iv[16]; // zeroes + uint8_t rsc[8]; // zeros + uint8_t reserved[8]; // zeroes + uint8_t mic[16]; // calculated data + uint16_t data_len; // various other things we don't need. +// uint8_t data[]; +} CC_HINT(__packed__) eapol_key_frame_t; + +typedef struct eapol_attr_t { + uint8_t header[4]; // 02030075 + eapol_key_frame_t frame; +} CC_HINT(__packed__) eapol_attr_t; + +#ifdef HAVE_PTHREAD_H +#define PTHREAD_MUTEX_LOCK pthread_mutex_lock +#define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock +#else +#define PTHREAD_MUTEX_LOCK(_x) +#define PTHREAD_MUTEX_UNLOCK(_x) +#endif + +typedef struct rlm_dpsk_s rlm_dpsk_t; + +typedef struct { + uint8_t mac[6]; + uint8_t pmk[32]; + + uint8_t *ssid; + size_t ssid_len; + + char *identity; + size_t identity_len; + + uint8_t *psk; + size_t psk_len; + time_t expires; + + fr_dlist_t dlist; + rlm_dpsk_t *inst; +} rlm_dpsk_cache_t; + +struct rlm_dpsk_s { + char const *xlat_name; + bool ruckus; + + rbtree_t *cache; + + uint32_t cache_size; + uint32_t cache_lifetime; + + char const *filename; + +#ifdef HAVE_PTHREAD_H + pthread_mutex_t mutex; +#endif + fr_dlist_t head; + + DICT_ATTR const *ssid; + DICT_ATTR const *anonce; + DICT_ATTR const *frame; +}; + +static const CONF_PARSER module_config[] = { + { "ruckus", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_dpsk_t, ruckus), "no" }, + + { "cache_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_dpsk_t, cache_size), "0" }, + { "cache_lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_dpsk_t, cache_lifetime), "0" }, + + { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_dpsk_t, filename), NULL }, + + CONF_PARSER_TERMINATOR +}; + + +static inline CC_HINT(nonnull) rlm_dpsk_cache_t *fr_dlist_head(fr_dlist_t const *head) +{ + if (head->prev == head) return NULL; + + return (rlm_dpsk_cache_t *) (((uintptr_t) head->next) - offsetof(rlm_dpsk_cache_t, dlist)); +} + +static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len) +{ + int i; + char buffer[2048]; /* large enough for largest len */ + + /* + * Leave a trailing space, we don't really care about that. + */ + for (i = 0; i < len; i++) { + snprintf(buffer + i * 2, sizeof(buffer) - i * 2, "%02x", data[i]); + } + + RDEBUG("%s %s", prefix, buffer); +} +#define RDEBUG_HEX if (rad_debug_lvl >= 3) rdebug_hex + +#if 0 +/* + * Find the Ruckus attributes, and convert to FreeRADIUS ones. + * + * Also check the WPA2 cipher. We need AES + HMAC-SHA1. + */ +static bool normalize(rlm_dpsk_t *inst, REQUEST *request) +{ + VALUE_PAIR *bssid, *cipher, *anonce, *key_msg, *vp; + + if (!inst->ruckus) return false; + + bssid = fr_pair_find_by_num(request->packet->vps, PW_RUCKUS_BSSID, VENDORPEC_RUCKUS, TAG_ANY); + if (!bssid) return false; + + cipher = fr_pair_find_by_num(request->packet->vps, PW_RUCKUS_DPSK_CIPHER, VENDORPEC_RUCKUS, TAG_ANY); + if (!cipher) return false; + + if (cipher->vp_byte != 4) { + RDEBUG("Found Ruckus-DPSK-Cipher != 4, which means that we cannot do DPSK"); + return false; + } + + anonce = fr_pair_find_by_num(request->packet->vps, PW_RUCKUS_DPSK_ANONCE, VENDORPEC_RUCKUS, TAG_ANY); + if (!anonce) return false; + + key_msg = fr_pair_find_by_num(request->packet->vps, PW_RUCKUS_DPSK_EAPOL_KEY_FRAME, VENDORPEC_RUCKUS, TAG_ANY); + if (!key_msg) return false; + + MEM(vp = fr_pair_afrom_da(request->packet, anonce->da)); + fr_pair_value_memcpy(vp, anonce->vp_octets, anonce->vp_length); + fr_pair_add(&request->packet->vps, vp); + + MEM(vp = fr_pair_afrom_da(request->packet, key_msg->da)); + fr_pair_value_memcpy(vp, key_msg->vp_octets, key_msg->vp_length); + fr_pair_add(&request->packet->vps, vp); + + return false; +} +#endif + +/* + * mod_authorize() - authorize user if we can authenticate + * it later. Add Auth-Type attribute if present in module + * configuration (usually Auth-Type must be "DPSK") + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void * instance, REQUEST *request) +{ + rlm_dpsk_t *inst = instance; + + if (!fr_pair_find_by_da(request->packet->vps, inst->anonce, TAG_ANY) && + !fr_pair_find_by_da(request->packet->vps, inst->frame, TAG_ANY)) { + return RLM_MODULE_NOOP; + } + + if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY)) { + RWDEBUG2("Auth-Type already set. Not setting to %s", inst->xlat_name); + return RLM_MODULE_NOOP; + } + + RDEBUG2("Found %s. Setting 'Auth-Type = %s'", inst->frame->name, inst->xlat_name); + + /* + * Set Auth-Type to MS-CHAP. The authentication code + * will take care of turning cleartext passwords into + * NT/LM passwords. + */ + if (!pair_make_config("Auth-Type", inst->xlat_name, T_OP_EQ)) { + return RLM_MODULE_FAIL; + } + + return RLM_MODULE_OK; +} + +static rlm_dpsk_cache_t *dpsk_cache_find(REQUEST *request, rlm_dpsk_t const *inst, uint8_t *buffer, size_t buflen, VALUE_PAIR *ssid, uint8_t const *mac) +{ + rlm_dpsk_cache_t *entry, my_entry; + + memcpy(my_entry.mac, mac, sizeof(my_entry.mac)); + memcpy(&my_entry.ssid, &ssid->vp_octets, sizeof(my_entry.ssid)); /* const issues */ + my_entry.ssid_len = ssid->vp_length; + + entry = rbtree_finddata(inst->cache, &my_entry); + if (entry) { + if (entry->expires > request->timestamp) { + RDEBUG3("Cache entry found"); + memcpy(buffer, entry->pmk, buflen); + return entry; + } + + RDEBUG3("Cache entry has expired"); + rbtree_deletebydata(inst->cache, entry); + } + + return NULL; +} + + +static int generate_pmk(REQUEST *request, rlm_dpsk_t const *inst, uint8_t *buffer, size_t buflen, VALUE_PAIR *ssid, uint8_t const *mac, char const *psk, size_t psk_len) +{ + VALUE_PAIR *vp; + + fr_assert(buflen == 32); + + if (!ssid) { + ssid = fr_pair_find_by_da(request->packet->vps, inst->ssid, TAG_ANY); + if (!ssid) { + RDEBUG("No %s in the request", inst->ssid->name); + return 0; + } + } + + /* + * No provided PSK. Try to look it up in the cache. If + * it isn't there, find it in the config items. + */ + if (!psk) { + if (inst->cache && mac) { + rlm_dpsk_cache_t *entry; + + entry = dpsk_cache_find(request, inst, buffer, buflen, ssid, mac); + if (entry) { + memcpy(buffer, entry->pmk, buflen); + return 1; + } + RDEBUG3("Cache entry not found"); + } /* else no caching */ + + vp = fr_pair_find_by_num(request->config, PW_PRE_SHARED_KEY, 0, TAG_ANY); + if (!vp) { + RDEBUG("No &config:Pre-Shared-Key"); + return 0; + } + + psk = vp->vp_strvalue; + psk_len = vp->vp_length; + } + + if (PKCS5_PBKDF2_HMAC_SHA1((const char *) psk, psk_len, (const unsigned char *) ssid->vp_strvalue, ssid->vp_length, 4096, buflen, buffer) == 0) { + RDEBUG("Failed calling OpenSSL to calculate the PMK"); + return 0; + } + + return 1; +} + +/* + * Verify the DPSK information. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) +{ + rlm_dpsk_t *inst = instance; + VALUE_PAIR *anonce, *key_msg, *ssid, *vp; + rlm_dpsk_cache_t *entry; + int lineno = 0; + size_t len, psk_len; + unsigned int digest_len, mic_len; + eapol_attr_t const *eapol; + eapol_attr_t *zeroed; + FILE *fp = NULL; + char const *psk_identity = NULL, *psk; + uint8_t *p; + uint8_t const *snonce, *ap_mac; + uint8_t const *min_mac, *max_mac; + uint8_t const *min_nonce, *max_nonce; + uint8_t pmk[32]; + uint8_t s_mac[6], message[sizeof("Pairwise key expansion") + 6 + 6 + 32 + 32 + 1], frame[128]; + uint8_t digest[EVP_MAX_MD_SIZE], mic[EVP_MAX_MD_SIZE]; + char token_identity[256]; + + /* + * Search for the information in a bunch of attributes. + */ + anonce = fr_pair_find_by_da(request->packet->vps, inst->anonce, TAG_ANY); + if (!anonce) { + RDEBUG("No FreeRADIUS-802.1X-Anonce in the request"); + return RLM_MODULE_NOOP; + } + + if (anonce->vp_length != 32) { + RDEBUG("%s has incorrect length (%zu, not 32)", inst->anonce->name, anonce->vp_length); + return RLM_MODULE_NOOP; + } + + key_msg = fr_pair_find_by_da(request->packet->vps, inst->frame, TAG_ANY); + if (!key_msg) { + RDEBUG("No %s in the request", inst->frame->name); + return RLM_MODULE_NOOP; + } + + if (key_msg->vp_length < sizeof(*eapol)) { + RDEBUG("%s has incorrect length (%zu < %zu)", inst->frame->name, key_msg->vp_length, sizeof(*eapol)); + return RLM_MODULE_NOOP; + } + + if (key_msg->vp_length > sizeof(frame)) { + RDEBUG("%s has incorrect length (%zu > %zu)", inst->frame->name, key_msg->vp_length, sizeof(frame)); + return RLM_MODULE_NOOP; + } + + ssid = fr_pair_find_by_da(request->packet->vps, inst->ssid, TAG_ANY); + if (!ssid) { + RDEBUG("No %s in the request", inst->ssid->name); + return 0; + } + + /* + * Get supplicant MAC address. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); + if (!vp) { + RDEBUG("No &User-Name"); + return RLM_MODULE_NOOP; + } + + len = fr_hex2bin(s_mac, sizeof(s_mac), vp->vp_strvalue, vp->vp_length); + if (len != 6) { + RDEBUG("&User-Name is not a recognizable hex MAC address"); + return RLM_MODULE_NOOP; + } + + /* + * In case we're not reading from a file. + */ + vp = fr_pair_find_by_num(request->config, PW_PSK_IDENTITY, 0, TAG_ANY); + if (vp) psk_identity = vp->vp_strvalue; + + vp = fr_pair_find_by_num(request->config, PW_PRE_SHARED_KEY, 0, TAG_ANY); + if (vp) { + psk = vp->vp_strvalue; + psk_len = vp->vp_length; + } else { + psk = NULL; + psk_len = 0; + } + + /* + * Get the AP MAC address. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_CALLED_STATION_MAC, 0, TAG_ANY); + if (!vp) { + RDEBUG("No &Called-Station-MAC"); + return RLM_MODULE_NOOP; + } + + if (vp->length != 6) { + RDEBUG("&Called-Station-MAC is not a recognizable MAC address"); + return RLM_MODULE_NOOP; + } + + ap_mac = vp->vp_octets; + + /* + * Sort the MACs + */ + if (memcmp(s_mac, ap_mac, 6) <= 0) { + min_mac = s_mac; + max_mac = ap_mac; + } else { + min_mac = ap_mac; + max_mac = s_mac; + } + + eapol = (eapol_attr_t const *) key_msg->vp_octets; + + /* + * Get supplicant nonce and AP nonce. + * + * Then sort the nonces. + */ + snonce = key_msg->vp_octets + 17; + if (memcmp(snonce, anonce->vp_octets, 32) <= 0) { + min_nonce = snonce; + max_nonce = anonce->vp_octets; + } else { + min_nonce = anonce->vp_octets; + max_nonce = snonce; + } + + /* + * Create the base message which we will hash. + */ + memcpy(message, "Pairwise key expansion", sizeof("Pairwise key expansion")); /* including trailing NUL */ + p = &message[sizeof("Pairwise key expansion")]; + + memcpy(p, min_mac, 6); + memcpy(p + 6, max_mac, 6); + p += 12; + + memcpy(p, min_nonce, 32); + memcpy(p + 32, max_nonce, 32); + p += 64; + *p = '\0'; + fr_assert(sizeof(message) == (p + 1 - message)); + + if (inst->filename && !psk) { + FR_TOKEN token; + char const *q; + char token_psk[256]; + char token_mac[256]; + char buffer[1024]; + + /* + * If there's a cached entry, we don't read the file. + */ + entry = dpsk_cache_find(request, inst, pmk, sizeof(pmk), ssid, s_mac); + if (entry) { + psk_identity = entry->identity; + goto make_digest; + } + + RDEBUG3("Looking for PSK in file %s", inst->filename); + + fp = fopen(inst->filename, "r"); + if (!fp) { + REDEBUG("Failed opening %s - %s", inst->filename, fr_syserror(errno)); + return RLM_MODULE_FAIL; + } + +get_next_psk: + q = fgets(buffer, sizeof(buffer), fp); + if (!q) { + RDEBUG("Failed to find matching key in %s", inst->filename); + fail: + fclose(fp); + return RLM_MODULE_FAIL; + } + + /* + * Split the line on commas, paying attention to double quotes. + */ + token = getstring(&q, token_identity, sizeof(token_identity), true); + if (token == T_INVALID) { + RDEBUG("%s[%d] Failed parsing identity", inst->filename, lineno); + goto fail; + } + + if (*q != ',') { + RDEBUG("%s[%d] Failed to find ',' after identity", inst->filename, lineno); + goto fail; + } + q++; + + token = getstring(&q, token_psk, sizeof(token_psk), true); + if (token == T_INVALID) { + RDEBUG("%s[%d] Failed parsing PSK", inst->filename, lineno); + goto fail; + } + + if (*q == ',') { + q++; + + token = getstring(&q, token_mac, sizeof(token_mac), true); + if (token == T_INVALID) { + RDEBUG("%s[%d] Failed parsing MAC", inst->filename, lineno); + goto fail; + } + + /* + * See if the MAC matches. If not, skip + * this entry. That's a basic negative cache. + */ + if ((strlen(token_mac) != 12) || + (fr_hex2bin((uint8_t *) token_mac, 6, token_mac, 12) != 12)) { + RDEBUG("%s[%d] Failed parsing MAC", inst->filename, lineno); + goto fail; + } + + if (memcmp(s_mac, token_mac, 6) != 0) { + psk_identity = NULL; + goto get_next_psk; + } + + /* + * Close the file so that we don't check any other entries. + */ + MEM(vp = fr_pair_afrom_num(request, PW_PRE_SHARED_KEY, 0)); + fr_pair_value_bstrncpy(vp, token_psk, strlen(token_psk)); + + fr_pair_add(&request->config, vp); + fclose(fp); + fp = NULL; + + RDEBUG3("Found matching MAC"); + } + + /* + * Generate the PMK using the SSID, this MAC, and the PSK we just read. + */ + RDEBUG3("%s[%d] Trying PSK %s", inst->filename, lineno, token_psk); + if (generate_pmk(request, inst, pmk, sizeof(pmk), ssid, s_mac, token_psk, strlen(token_psk)) == 0) { + RDEBUG("No &config:Pairwise-Master-Key or &config:Pre-Shared-Key found"); + return RLM_MODULE_NOOP; + } + + /* + * Remember which identity we had + */ + psk_identity = token_identity; + goto make_digest; + } + + /* + * Use the PMK if it already exists. Otherwise calculate it from the PSK. + */ + vp = fr_pair_find_by_num(request->config, PW_PAIRWISE_MASTER_KEY, 0, TAG_ANY); + if (!vp) { + if (generate_pmk(request, inst, pmk, sizeof(pmk), ssid, s_mac, psk, psk_len) == 0) { + RDEBUG("No &config:Pairwise-Master-Key or &config:Pre-Shared-Key found"); + fr_assert(!fp); + return RLM_MODULE_NOOP; + } + + } else if (vp->vp_length != sizeof(pmk)) { + RDEBUG("Pairwise-Master-Key has incorrect length (%zu != %zu)", vp->vp_length, sizeof(pmk)); + fr_assert(!fp); + return RLM_MODULE_NOOP; + + } else { + memcpy(pmk, vp->vp_octets, sizeof(pmk)); + } + + /* + * HMAC = HMAC_SHA1(pmk, message); + * + * We need the first 16 octets of this. + */ +make_digest: + digest_len = sizeof(digest); + HMAC(EVP_sha1(), pmk, sizeof(pmk), message, sizeof(message), digest, &digest_len); + + RDEBUG_HEX(request, "message:", message, sizeof(message)); + RDEBUG_HEX(request, "pmk :", pmk, sizeof(pmk)); + RDEBUG_HEX(request, "kck :", digest, 16); + + /* + * Create the frame with the middle field zero, and hash it with the KCK digest we calculated from the key expansion. + */ + memcpy(frame, key_msg->vp_octets, key_msg->vp_length); + zeroed = (eapol_attr_t *) &frame[0]; + memset(&zeroed->frame.mic[0], 0, 16); + + RDEBUG_HEX(request, "zeroed:", frame, key_msg->vp_length); + + mic_len = sizeof(mic); + HMAC(EVP_sha1(), digest, 16, frame, key_msg->vp_length, mic, &mic_len); + + /* + * Do the MICs match? + */ + if (memcmp(&eapol->frame.mic[0], mic, 16) != 0) { + if (fp) { + psk_identity = NULL; + goto get_next_psk; + } + + RDEBUG_HEX(request, "calculated mic:", mic, 16); + RDEBUG_HEX(request, "packet mic :", &eapol->frame.mic[0], 16); + return RLM_MODULE_FAIL; + } + + /* + * It matches. Close the input file if necessary. + */ + if (fp) fclose(fp); + + /* + * Extend the lifetime of the cache entry, or add the + * cache entry if necessary. + */ + if (inst->cache) { + rlm_dpsk_cache_t my_entry; + + /* + * Find the entry (again), and update the expiry time. + * + * Create the entry if neessary. + */ + memcpy(my_entry.mac, s_mac, sizeof(my_entry.mac)); + + vp = fr_pair_find_by_da(request->packet->vps, inst->ssid, TAG_ANY); + if (!vp) goto save_psk; /* should never really happen, but just to be safe */ + + memcpy(&my_entry.ssid, &vp->vp_octets, sizeof(my_entry.ssid)); /* const issues */ + my_entry.ssid_len = vp->vp_length; + + entry = rbtree_finddata(inst->cache, &my_entry); + if (!entry) { + /* + * Too many entries in the cache. Delete the oldest one. + */ + if (rbtree_num_elements(inst->cache) > inst->cache_size) { + PTHREAD_MUTEX_LOCK(&inst->mutex); + entry = fr_dlist_head(&inst->head); + PTHREAD_MUTEX_UNLOCK(&inst->mutex); + + rbtree_deletebydata(inst->cache, entry); + } + + MEM(entry = talloc_zero(NULL, rlm_dpsk_cache_t)); + + memcpy(entry->mac, s_mac, sizeof(entry->mac)); + memcpy(entry->pmk, pmk, sizeof(entry->pmk)); + + fr_dlist_entry_init(&entry->dlist); + entry->inst = inst; + + /* + * Save the variable-length SSID. + */ + MEM(entry->ssid = talloc_memdup(entry, vp->vp_octets, vp->vp_length)); + entry->ssid_len = vp->vp_length; + + /* + * Save the PSK. If we just have the + * PMK, then we can still cache that. + */ + vp = fr_pair_find_by_num(request->config, PW_PRE_SHARED_KEY, 0, TAG_ANY); + if (vp) { + MEM(entry->psk = talloc_memdup(entry, vp->vp_octets, vp->vp_length)); + entry->psk_len = vp->vp_length; + } + + /* + * Save the identity. + */ + if (psk_identity) { + MEM(entry->identity = talloc_memdup(entry, psk_identity, strlen(psk_identity))); + entry->identity_len = strlen(psk_identity); + } + + /* + * Cache it. + */ + if (!rbtree_insert(inst->cache, entry)) { + talloc_free(entry); + goto save_found_psk; + } + RDEBUG3("Cache entry saved"); + } + entry->expires = request->timestamp + inst->cache_lifetime; + + PTHREAD_MUTEX_LOCK(&inst->mutex); + fr_dlist_entry_unlink(&entry->dlist); + fr_dlist_insert_tail(&inst->head, &entry->dlist); + PTHREAD_MUTEX_UNLOCK(&inst->mutex); + + /* + * Add the PSK to the reply items, if it was cached. + */ + if (entry->psk) { + MEM(vp = fr_pair_afrom_num(request->reply, PW_PRE_SHARED_KEY, 0)); + fr_pair_value_bstrncpy(vp, entry->psk, entry->psk_len); + + fr_pair_add(&request->reply->vps, vp); + } + + goto save_psk_identity; + } + + /* + * Save a copy of the found PSK in the reply; + */ +save_psk: + vp = fr_pair_find_by_num(request->config, PW_PRE_SHARED_KEY, 0, TAG_ANY); + +save_found_psk: + if (!vp) return RLM_MODULE_OK; + + fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); + +save_psk_identity: + /* + * Save which identity matched. + */ + if (psk_identity) { + MEM(vp = fr_pair_afrom_num(request->reply, PW_PSK_IDENTITY, 0)); + fr_pair_value_bstrncpy(vp, psk_identity, strlen(psk_identity)); + + fr_pair_add(&request->reply->vps, vp); + } + + return RLM_MODULE_OK; +} + +/* + * Generate the PMK from SSID and Pre-Shared-Key + */ +static ssize_t dpsk_xlat(void *instance, REQUEST *request, + char const *fmt, char *out, size_t outlen) +{ + rlm_dpsk_t *inst = instance; + char const *p, *ssid, *psk; + size_t ssid_len, psk_len; + uint8_t buffer[32]; + + /* + * Prefer xlat arguments. But if they don't exist, use the attributes. + */ + p = fmt; + while (isspace((uint8_t) *p)) p++; + + if (!*p) { + if (generate_pmk(request, inst, buffer, sizeof(buffer), NULL, NULL, NULL, 0) == 0) { + RDEBUG("No &request:Called-Station-SSID or &config:Pre-Shared-Key found"); + return 0; + } + } else { + ssid = p; + + while (*p && !isspace((uint8_t) *p)) p++; + + ssid_len = p - ssid; + + if (!*p) { + REDEBUG("Found SSID, but no PSK"); + return 0; + } + + psk = p; + + while (*p && !isspace((uint8_t) *p)) p++; + + psk_len = p - psk; + + if (PKCS5_PBKDF2_HMAC_SHA1(psk, psk_len, (const unsigned char *) ssid, ssid_len, 4096, sizeof(buffer), buffer) == 0) { + RDEBUG("Failed calling OpenSSL to calculate the PMK"); + return 0; + } + } + + if (outlen < sizeof(buffer) * 2 + 1) { + REDEBUG("Output buffer is too small for PMK"); + return 0; + } + + return fr_bin2hex(out, buffer, 32); +} + +static int mod_bootstrap(CONF_SECTION *conf, void *instance) +{ + char const *name; + rlm_dpsk_t *inst = instance; + + /* + * Create the dynamic translation. + */ + name = cf_section_name2(conf); + if (!name) name = cf_section_name1(conf); + inst->xlat_name = name; + xlat_register(inst->xlat_name, dpsk_xlat, NULL, inst); + + if (inst->ruckus) { + inst->ssid = dict_attrbyvalue(PW_RUCKUS_BSSID, VENDORPEC_RUCKUS); + inst->anonce = dict_attrbyvalue(PW_RUCKUS_DPSK_ANONCE, VENDORPEC_RUCKUS); + inst->frame = dict_attrbyvalue(PW_RUCKUS_DPSK_EAPOL_KEY_FRAME, VENDORPEC_RUCKUS); + } else { + inst->ssid = dict_attrbyvalue(PW_CALLED_STATION_SSID, 0); + inst->anonce = dict_attrbyvalue(PW_FREERADIUS_8021X_ANONCE, VENDORPEC_FREERADIUS_EVS5); + inst->frame = dict_attrbyvalue(PW_FREERADIUS_8021X_EAPOL_KEY_MSG, VENDORPEC_FREERADIUS_EVS5); + } + + if (!inst->ssid || !inst->anonce || !inst->frame) { + cf_log_err_cs(conf, "Failed to find attributes in the dictionary. Please do not edit the default dictionaries!"); + return -1; + } + + return 0; +} + +static int cmp_cache_entry(void const *one, void const *two) +{ + rlm_dpsk_cache_t const *a = (rlm_dpsk_cache_t const *) one; + rlm_dpsk_cache_t const *b = (rlm_dpsk_cache_t const *) two; + int rcode; + + rcode = memcmp(a->mac, b->mac, sizeof(a->mac)); + if (rcode != 0) return rcode; + + if (a->ssid_len < b->ssid_len) return -1; + if (a->ssid_len > b->ssid_len) return +1; + + return memcmp(a->ssid, b->ssid, a->ssid_len); +} + +static void free_cache_entry(void *data) +{ + rlm_dpsk_cache_t *entry = (rlm_dpsk_cache_t *) data; + + PTHREAD_MUTEX_LOCK(&entry->inst->mutex); + fr_dlist_entry_unlink(&entry->dlist); + PTHREAD_MUTEX_UNLOCK(&entry->inst->mutex); + + talloc_free(entry); +} + +static int mod_instantiate(CONF_SECTION *conf, void *instance) +{ + rlm_dpsk_t *inst = instance; + + if (!inst->cache_size) return 0; + + FR_INTEGER_BOUND_CHECK("cache_size", inst->cache_size, <=, ((uint32_t) 1) << 16); + + if (!inst->cache_size) return 0; + + FR_INTEGER_BOUND_CHECK("cache_lifetime", inst->cache_lifetime, <=, (7 * 86400)); + FR_INTEGER_BOUND_CHECK("cache_lifetime", inst->cache_lifetime, >=, 3600); + + inst->cache = rbtree_create(inst, cmp_cache_entry, free_cache_entry, RBTREE_FLAG_LOCK); + if (!inst->cache) { + cf_log_err_cs(conf, "Failed creating internal cache"); + return -1; + } + + fr_dlist_entry_init(&inst->head); +#ifdef HAVE_PTHREAD_H + if (pthread_mutex_init(&inst->mutex, NULL) < 0) { + cf_log_err_cs(conf, "Failed creating mutex"); + return -1; + } +#endif + + return 0; +} + +#ifdef HAVE_PTHREAD_H +static int mod_detach(void *instance) +{ + rlm_dpsk_t *inst = instance; + + if (!inst->cache_size) return 0; + + pthread_mutex_destroy(&inst->mutex); + return 0; +} +#endif + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + * + * If the module needs to temporarily modify it's instantiation + * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE. + * The server will then take care of ensuring that the module + * is single-threaded. + */ +extern module_t rlm_dpsk; +module_t rlm_dpsk = { + .magic = RLM_MODULE_INIT, + .name = "dpsk", + .type = RLM_TYPE_THREAD_SAFE, + .inst_size = sizeof(rlm_dpsk_t), + .config = module_config, + .bootstrap = mod_bootstrap, + .instantiate = mod_instantiate, +#ifdef HAVE_PTHREAD_H + .detach = mod_detach, +#endif + .methods = { + [MOD_AUTHORIZE] = mod_authorize, + [MOD_AUTHENTICATE] = mod_authenticate, + }, +}; diff --git a/src/modules/rlm_eap/eap.c b/src/modules/rlm_eap/eap.c index 1ece323..5c0bba0 100644 --- a/src/modules/rlm_eap/eap.c +++ b/src/modules/rlm_eap/eap.c @@ -803,7 +803,7 @@ int eap_start(rlm_eap_t *inst, REQUEST *request) ((eap_msg->vp_octets[4] == 0) || (eap_msg->vp_octets[4] >= PW_EAP_MAX_TYPES) || (!inst->methods[eap_msg->vp_octets[4]]))) { - RDEBUG2("Ignoring Unknown EAP type"); + RDEBUG2("Ignoring Unknown EAP type %02x", eap_msg->vp_octets[4]); return EAP_NOOP; } @@ -833,6 +833,8 @@ int eap_start(rlm_eap_t *inst, REQUEST *request) } if ((eap_msg->vp_octets[4] == PW_EAP_TTLS) || + (eap_msg->vp_octets[4] == PW_EAP_FAST) || + (eap_msg->vp_octets[4] == PW_EAP_TEAP) || (eap_msg->vp_octets[4] == PW_EAP_PEAP)) { RDEBUG2("Continuing tunnel setup"); return EAP_OK; @@ -1214,7 +1216,7 @@ eap_handler_t *eap_handler(rlm_eap_t *inst, eap_packet_raw_t **eap_packet_p, } } } else { /* packet was EAP identity */ - handler = eap_handler_alloc(inst); + handler = eap_handler_alloc(inst, request); if (!handler) { goto error; } @@ -1224,10 +1226,14 @@ eap_handler_t *eap_handler(rlm_eap_t *inst, eap_packet_raw_t **eap_packet_p, */ handler->identity = eap_identity(request, handler, eap_packet); if (!handler->identity) { - RDEBUG("Identity Unknown, authentication failed"); - error2: - talloc_free(handler); - goto error; + if (!inst->allow_empty_identities) { + RDEBUG("Identity Unknown, authentication failed"); + error2: + talloc_free(handler); + goto error; + } + + handler->identity = ""; } vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); diff --git a/src/modules/rlm_eap/eap.h b/src/modules/rlm_eap/eap.h index b487c08..0724463 100644 --- a/src/modules/rlm_eap/eap.h +++ b/src/modules/rlm_eap/eap.h @@ -60,7 +60,6 @@ typedef enum operation_t { PROCESS } operation_t; - /* * eap_handler_t is the interface for any EAP-Type. * Each handler contains information for one specific EAP-Type. @@ -103,7 +102,9 @@ typedef struct _eap_handler { REQUEST *request; - char *identity; //!< User name from EAP-Identity + char const *identity; //!< User name from EAP-Identity + + char const *dedup; //!< dedup key EAP_DS *prev_eapds; EAP_DS *eap_ds; diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c index 2f37663..3a915bc 100644 --- a/src/modules/rlm_eap/libeap/eap_tls.c +++ b/src/modules/rlm_eap/libeap/eap_tls.c @@ -69,6 +69,9 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_ handler->tls = true; + tls_conf->name = dict_valnamebyattr(PW_EAP_TYPE, 0, handler->type); + if (!tls_conf->name) tls_conf->name = "???"; + /* * Every new session is started only from EAP-TLS-START. * Before Sending EAP-TLS-START, open a new SSL session. @@ -108,23 +111,22 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_ */ int eaptls_start(EAP_DS *eap_ds, int peap_flag) { - EAPTLS_PACKET reply; + EAPTLS_PACKET reply; - reply.code = FR_TLS_START; - reply.length = TLS_HEADER_LEN + 1/*flags*/; + reply.code = FR_TLS_START; + reply.length = TLS_HEADER_LEN + 1/*flags*/; - reply.flags = peap_flag; - reply.flags = SET_START(reply.flags); + reply.flags = peap_flag; + reply.flags = SET_START(reply.flags); - reply.data = NULL; - reply.dlen = 0; + reply.data = NULL; + reply.dlen = 0; - eaptls_compose(eap_ds, &reply); + eaptls_compose(eap_ds, &reply); - return 1; + return 1; } - /** Send an EAP-TLS success * * Composes an EAP-TLS-Success. This is a message with code EAP_TLS_ESTABLISHED. @@ -186,12 +188,11 @@ int eaptls_success(eap_handler_t *handler, int peap_flag) /* Should never happen */ rad_assert(0); return 0; - break; } eaptls_gen_mppe_keys(request, tls_session->ssl, tls_session->label, context, context_size); - } else if (handler->type != PW_EAP_FAST) { + } else if ((handler->type != PW_EAP_FAST) && (handler->type != PW_EAP_TEAP)) { RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label"); } @@ -230,13 +231,21 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) * EAP-Request. We always embed the TLS-length in all EAP-TLS * packets that we send, for easy reference purpose. Handle * fragmentation and sending the next fragment etc. + * + * FIXME: support fragmented start due to TEAP outer tlvs */ -int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) +int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn, bool start) { EAPTLS_PACKET reply; unsigned int size; - unsigned int nlen; unsigned int lbit = 0; + unsigned int obit = 0; + VALUE_PAIR *vp; + vp_cursor_t cursor; + uint32_t nlen; + uint16_t ohdr[2]; + uint32_t olen = 0; + uint32_t tls_mtu; /* This value determines whether we set (L)ength flag for EVERY packet we send and add corresponding @@ -257,16 +266,46 @@ int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) if (ssn->length_flag) { lbit = 4; } + + /* + * This is included in the first fragment, and then never + * afterwards. + */ + if (start && ssn->outer_tlvs) { + for (vp = fr_cursor_init(&cursor, &ssn->outer_tlvs); + vp; + vp = fr_cursor_next(&cursor)) { + if (vp->da->type != PW_TYPE_OCTETS) { + DEBUG("FIXME Outer-TLV %s is of not type octets", vp->da->name); + continue; + } + obit = 4; + olen += sizeof(ohdr) + vp->vp_length; + break; + } + } + if (ssn->fragment == 0) { ssn->tls_msg_len = ssn->dirty_out.used; } - reply.code = FR_TLS_REQUEST; + reply.code = start ? FR_TLS_START : FR_TLS_REQUEST; reply.flags = ssn->peap_flag; + if (start) reply.flags = SET_START(reply.flags); + + /* + * This is only true for TEAP, so only TEAP has to check the return value of this function. + */ + if (lbit + obit + olen >= ssn->mtu) { + ERROR("fragment_size is too small for outer TLVs"); + return -1; + } + + tls_mtu = ssn->mtu - lbit - obit - olen; /* Send data, NOT more than the FRAGMENT size */ - if (ssn->dirty_out.used > ssn->mtu) { - size = ssn->mtu; + if (ssn->dirty_out.used > tls_mtu) { + size = tls_mtu; reply.flags = SET_MORE_FRAGMENTS(reply.flags); /* Length MUST be included if it is the First Fragment */ if (ssn->fragment == 0) { @@ -278,7 +317,7 @@ int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) ssn->fragment = 0; } - reply.dlen = lbit + size; + reply.dlen = lbit + obit + size + olen; reply.length = TLS_HEADER_LEN + 1/*flags*/ + reply.dlen; reply.data = talloc_array(eap_ds, uint8_t, reply.length); @@ -286,10 +325,59 @@ int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) if (lbit) { nlen = htonl(ssn->tls_msg_len); - memcpy(reply.data, &nlen, lbit); + memcpy(reply.data, &nlen, sizeof(nlen)); reply.flags = SET_LENGTH_INCLUDED(reply.flags); } - (ssn->record_minus)(&ssn->dirty_out, reply.data + lbit, size); + + if (obit) { + nlen = 0; + for (vp = fr_cursor_init(&cursor, &ssn->outer_tlvs); + vp; + vp = fr_cursor_next(&cursor)) { + if (vp->da->type != PW_TYPE_OCTETS) continue; + nlen += sizeof(ohdr) + vp->vp_length; + } + + ssn->outer_tlvs_octets = talloc_array(ssn, uint8_t, olen); + if (!ssn->outer_tlvs_octets) return 0; + + nlen = htonl(nlen); + memcpy(reply.data + lbit, &nlen, sizeof(nlen)); + reply.flags = SET_OUTER_TLV_INCLUDED(reply.flags); + } + + (ssn->record_minus)(&ssn->dirty_out, reply.data + lbit + obit, size); + + /* + * Tack on the outer TLVs after the TLS data. + */ + if (obit) { + olen = 0; + for (vp = fr_cursor_init(&cursor, &ssn->outer_tlvs); + vp; + vp = fr_cursor_next(&cursor)) { + if (vp->da->type != PW_TYPE_OCTETS) continue; + + /* FIXME duplicates eap_teap_tlv_append */ + + /* + * RFC7170, Section 4.3.1 - Outer TLVs must be marked optional + */ + ohdr[0] = htons((vp->da->attr >> fr_attr_shift[1]) & fr_attr_mask[1]); + ohdr[1] = htons(vp->vp_length); + + /* use by Crypto-Binding TLV */ + memcpy(ssn->outer_tlvs_octets + olen, ohdr, sizeof(ohdr)); + olen += sizeof(ohdr); + memcpy(ssn->outer_tlvs_octets + olen, vp->vp_octets, vp->vp_length); + olen += vp->vp_length; + + memcpy(reply.data + lbit + obit + size, ohdr, sizeof(ohdr)); + size += sizeof(ohdr); + memcpy(reply.data + lbit + obit + size, vp->vp_octets, vp->vp_length); + size += vp->vp_length; + } + } eaptls_compose(eap_ds, &reply); talloc_free(reply.data); @@ -425,6 +513,9 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) * from a fragment acknowledgement. */ if (TLS_LENGTH_INCLUDED(eaptls_packet->flags)) { + /* + * data[0] and data[1] are always zero, vi eap_vp2packet() + */ size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3]; if (frag_len > total_len) { @@ -463,6 +554,14 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) return FR_TLS_FIRST_FRAGMENT; } + /* + * The "O" bit is only allowed for the first fragment. + */ + if (TLS_OUTER_TLV_INCLUDED(eaptls_packet->flags)) { + REDEBUG("(TLS) EAP Peer set 'O' bit after initial fragment"); + return FR_TLS_INVALID; + } + RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes). " "Peer says more fragments will follow", frag_len); @@ -494,6 +593,10 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) } /* + * eap_vp2packet() ensures that the 'O' bit is not set here. + */ + + /* * The previous packet had the M flags set, but this one doesn't, * this must be the final record fragment */ @@ -563,7 +666,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st { EAPTLS_PACKET *tlspacket; uint32_t data_len = 0; - uint32_t len = 0; + uint32_t obit = 0; uint8_t *data = NULL; if (status == FR_TLS_INVALID) return NULL; @@ -599,36 +702,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st tlspacket->flags = eap_ds->response->type.data[0]; /* - * A quick sanity check of the flags. If we've been told - * that there's a length, and there isn't one, then stop. + * eaptls_verify() ensures that all of the flags are correct. */ - if (TLS_LENGTH_INCLUDED(tlspacket->flags) && - (tlspacket->length < 5)) { /* flags + TLS message length */ - REDEBUG("(TLS) EAP Invalid packet received: Length bit is set," - "but packet too short to contain length field"); - talloc_free(tlspacket); - return NULL; - } - - /* - * If the final TLS packet is larger than we can handle, die - * now. - * - * Likewise, if the EAP packet says N bytes, and the TLS - * packet says there's fewer bytes, it's a problem. - */ - if (TLS_LENGTH_INCLUDED(tlspacket->flags)) { - memcpy(&data_len, &eap_ds->response->type.data[1], 4); - data_len = ntohl(data_len); - if (data_len > MAX_RECORD_SIZE) { - REDEBUG("(TLS) EAP Reassembled data will be %u bytes, " - "greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", - data_len); - talloc_free(tlspacket); - return NULL; - } - } - switch (status) { /* * The TLS Message Length field is four octets, and @@ -640,39 +715,28 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st * length should solve the problem. */ case FR_TLS_FIRST_FRAGMENT: - case FR_TLS_LENGTH_INCLUDED: - case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH: - if (tlspacket->length < 5) { /* flags + TLS message length */ - REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none"); - talloc_free(tlspacket); - return NULL; - } + obit = TLS_OUTER_TLV_INCLUDED(tlspacket->flags) << 2; /* - * Extract all the TLS fragments from the - * previous eap_ds Start appending this - * fragment to the above ds + * @todo - decode outer TLVs, too */ - memcpy(&data_len, &eap_ds->response->type.data[1], sizeof(uint32_t)); - data_len = ntohl(data_len); - data = (eap_ds->response->type.data + 5/*flags+TLS-Length*/); - len = eap_ds->response->type.length - 5/*flags+TLS-Length*/; - /* - * Hmm... this should be an error, too. - */ - if (data_len > len) { - data_len = len; - } - break; + /* FALL-THROUGH */ + + case FR_TLS_LENGTH_INCLUDED: + case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH: + eap_ds->response->type.data += 4 + obit; + eap_ds->response->type.length -= 4 + obit; + + /* FALL-THROUGH */ /* * Data length is implicit, from the EAP header. */ case FR_TLS_MORE_FRAGMENTS: case FR_TLS_OK: - data_len = eap_ds->response->type.length - 1/*flags*/; - data = eap_ds->response->type.data + 1/*flags*/; + data_len = eap_ds->response->type.length - 1; + data = eap_ds->response->type.data + 1; break; default: @@ -689,6 +753,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st talloc_free(tlspacket); return NULL; } + memcpy(tlspacket->data, data, data_len); } @@ -784,7 +849,7 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h * TLS proper can decide what to do, then. */ if (tls_session->dirty_out.used > 0) { - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); return FR_TLS_HANDLED; } @@ -890,7 +955,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) * of fragments" phase. */ case FR_TLS_REQUEST: - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); status = FR_TLS_HANDLED; goto done; diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h index 8e5fc77..1112bcb 100644 --- a/src/modules/rlm_eap/libeap/eap_tls.h +++ b/src/modules/rlm_eap/libeap/eap_tls.h @@ -59,10 +59,14 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler); int eaptls_success(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); -int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull); +int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn, bool start) CC_HINT(nonnull); +#if OPENSSL_VERSION_NUMBER >= 0x10101000L +void TLS_PRF(SSL *ssl, unsigned char *sec, size_t seclen, struct iovec *iov, size_t iovcnt, unsigned char *key, size_t keylen); +#endif void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); +void eaptls_gen_keys_only(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size, uint8_t *out, size_t outlen); void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size); void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size); void eaptls_gen_eap_key(eap_handler_t *handler); diff --git a/src/modules/rlm_eap/libeap/eap_types.h b/src/modules/rlm_eap/libeap/eap_types.h index c6568ff..beee998 100644 --- a/src/modules/rlm_eap/libeap/eap_types.h +++ b/src/modules/rlm_eap/libeap/eap_types.h @@ -98,7 +98,9 @@ typedef enum eap_method { PW_EAP_GPSK, /* 51 */ PW_EAP_PWD, /* 52 */ PW_EAP_EKE, /* 53 */ - PW_EAP_MAX_TYPES /* 54 - for validation */ + PW_EAP_PT_EAP, /* 54 */ + PW_EAP_TEAP, /* 55 */ + PW_EAP_MAX_TYPES /* 56 - for validation */ } eap_type_t; #define PW_EAP_EXPANDED_TYPE (254) diff --git a/src/modules/rlm_eap/libeap/eapcommon.c b/src/modules/rlm_eap/libeap/eapcommon.c index 96db30b..5abe47a 100644 --- a/src/modules/rlm_eap/libeap/eapcommon.c +++ b/src/modules/rlm_eap/libeap/eapcommon.c @@ -302,6 +302,7 @@ eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps) uint16_t len; int total_len; vp_cursor_t cursor; + bool allow_o = false; /* * Get only EAP-Message attribute list @@ -315,7 +316,7 @@ eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps) /* * Sanity check the length before doing anything. */ - if (first->vp_length < 4) { + if (first->vp_length < 5) { fr_strerror_printf("EAP packet is too short"); return NULL; } @@ -330,8 +331,8 @@ eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps) /* * Take out even more weird things. */ - if (len < 4) { - fr_strerror_printf("EAP packet has invalid length (less than 4 bytes)"); + if (len < 5) { + fr_strerror_printf("EAP packet has invalid length (less than 5 bytes)"); return NULL; } @@ -379,6 +380,125 @@ eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps) ptr += i->vp_length; } + /* + * Do additional sanity check for TLS-based EAP types, so + * that we don't have to do any of that later. + */ + switch (eap_packet->data[0]) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Code | Identifier | Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Flags | Ver | Message Length : + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * : Message Length | + * + * Flags are: + * + * 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * |L M S O R R R R| + * +-+-+-+-+-+-+-+-+ + * + * L = Length included + * M = More fragments + * S = EAP-TLS start + * O = outer TLV length included (4 octets, only for TEAP) + * R = Reserved + */ + case PW_EAP_TEAP: + allow_o = true; + /* FALL-THROUGH */ + + case PW_EAP_TLS: + case PW_EAP_TTLS: + case PW_EAP_PEAP: + case PW_EAP_FAST: + if (len < 2) { + fr_strerror_printf("Malformed EAP packet - packet is too small to contain TLS flags"); + talloc_free(eap_packet); + return NULL; + } + + /* + * L bit set means we have 4 octets of Length + * following the flags field. + */ + if ((eap_packet->data[1] & 0x80) != 0) { + uint32_t tls_len; + + if (len <= (2 + 4)) { + fr_strerror_printf("Malformed EAP packet - TLS 'L' bit is set, but packet is too small to contain 'length' field"); + talloc_free(eap_packet); + return NULL; + } + + /* + * This is arguably wrong... a single TLS + * record is max 16K in length. But a + * TLS message may span multiple TLS + * records. + */ + memcpy(&tls_len, eap_packet->data + 2, 4); + tls_len = ntohl(tls_len); + if (tls_len > 16384) { + fr_strerror_printf("Malformed EAP packet - TLS reassembled data length %u (%08x) (will be greater than the TLS maximum record size of 16384 bytes", tls_len, tls_len); + talloc_free(eap_packet); + return NULL; + } + + /* + * O bit set means we have 4 octets of Outer TLV Length + * following the Length field. + */ + if ((eap_packet->data[1] & 0x10) != 0) { + uint32_t tlv_len; + + if (!allow_o) { + fr_strerror_printf("Malformed EAP packet - TLS 'O' bit is set, but EAP method does not use it."); + talloc_free(eap_packet); + return NULL; + } + + if (len <= (2 + 4 + 4)) { + fr_strerror_printf("Malformed EAP packet - TLS 'O' bit is set, but packet is too small to contain 'outer tlv length' field"); + talloc_free(eap_packet); + return NULL; + } + + memcpy(&tlv_len, eap_packet->data + 2 + 4, 4); + tlv_len = ntohl(tlv_len); + + /* + * The EAP header includes all of + * the data in the packet. The + * outer TLV length cannot + * include the EAP header, type, + * flags, length field, or outer + * tlv length field. + */ + if ((int)tlv_len > (len - (2 + 4 + 4))) { + fr_strerror_printf("Malformed EAP packet - TLS 'O' bit is set, but 'outer tlv length' field is larger than the current fragment"); + talloc_free(eap_packet); + return NULL; + } + } + } else { + if ((eap_packet->data[1] & 0x10) != 0) { + fr_strerror_printf("Malformed EAP packet - TLS 'O' bit is set, but 'L' bit is not set."); + talloc_free(eap_packet); + return NULL; + } + + } + break; + + default: + break; + } + return eap_packet; } diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c index 385441c..4356164 100644 --- a/src/modules/rlm_eap/libeap/mppe_keys.c +++ b/src/modules/rlm_eap/libeap/mppe_keys.c @@ -34,6 +34,35 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include <openssl/provider.h> #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000L +#include <openssl/kdf.h> + +void TLS_PRF(SSL *ssl, + unsigned char *sec, size_t seclen, + struct iovec *iov, size_t iovcnt, + unsigned char *key, size_t keylen) +{ + const EVP_MD *md = SSL_CIPHER_get_handshake_digest(SSL_get_current_cipher(ssl)); + EVP_MD *unconst_md; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); + + EVP_PKEY_derive_init(pctx); + + memcpy(&unconst_md, &md, sizeof(md)); /* const issues */ + EVP_PKEY_CTX_set_tls1_prf_md(pctx, unconst_md); + + EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, sec, seclen); + + for (unsigned int i = 0; i < iovcnt; i++) { + EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, iov[i].iov_base, iov[i].iov_len); + } + + EVP_PKEY_derive(pctx, key, &keylen); + + EVP_PKEY_CTX_free(pctx); +} +#endif + /* * TLS P_hash from RFC 2246/5246 section 5 */ @@ -210,23 +239,20 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len, #define EAPTLS_MPPE_KEY_LEN 32 /* - * Generate keys according to RFC 2716 and add to reply + * Generate keys according to RFC 5216 (section 2.3) */ -void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) +void eaptls_gen_keys_only(UNUSED REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size, uint8_t *out, size_t outlen) { - uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; - uint8_t *p; - size_t len; - - len = strlen(label); + size_t len = strlen(label); #if OPENSSL_VERSION_NUMBER >= 0x10001000L - if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) { + if (SSL_export_keying_material(s, out, outlen, label, len, context, context_size, context != NULL) != 1) { ERROR("Failed generating keying material"); return; } #else { + uint8_t *p; uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)]; uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN]; @@ -255,9 +281,20 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t c } PRF(s->session->master_key, s->session->master_key_length, - seed, len, out, buf, sizeof(out)); + seed, len, out, buf, outlen); } #endif +} + +/* + * Generate keys according to RFC 5216 (section 2.3) and add to reply + */ +void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) +{ + uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; + uint8_t *p; + + eaptls_gen_keys_only(request, s, label, context, context_size, out, sizeof(out)); p = out; eap_add_reply(request, "MS-MPPE-Recv-Key", p, EAPTLS_MPPE_KEY_LEN); @@ -268,7 +305,6 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t c eap_add_reply(request, "EAP-EMSK", out + 64, 64); } - #define FR_TLS_PRF_CHALLENGE "ttls challenge" /* diff --git a/src/modules/rlm_eap/mem.c b/src/modules/rlm_eap/mem.c index 6be8ca4..680c879 100644 --- a/src/modules/rlm_eap/mem.c +++ b/src/modules/rlm_eap/mem.c @@ -38,6 +38,9 @@ RCSID("$Id$") #define PTHREAD_MUTEX_UNLOCK(_x) #endif +static eap_handler_t *eaplist_delete(rlm_eap_t *inst, REQUEST *request, + eap_handler_t *handler, char const *msg, bool delete); + /* * Allocate a new eap_packet_t */ @@ -78,11 +81,6 @@ void eap_ds_free(EAP_DS **eap_ds_p) static int _eap_handler_free(eap_handler_t *handler) { - if (handler->identity) { - talloc_free(handler->identity); - handler->identity = NULL; - } - if (handler->prev_eapds) eap_ds_free(&(handler->prev_eapds)); if (handler->eap_ds) eap_ds_free(&(handler->eap_ds)); @@ -124,9 +122,10 @@ static int _eap_handler_free(eap_handler_t *handler) /* * Allocate a new eap_handler_t */ -eap_handler_t *eap_handler_alloc(rlm_eap_t *inst) +eap_handler_t *eap_handler_alloc(rlm_eap_t *inst, REQUEST *request) { - eap_handler_t *handler; + eap_handler_t *handler, *old; + char buffer[256]; handler = talloc_zero(NULL, eap_handler_t); if (!handler) { @@ -138,6 +137,20 @@ eap_handler_t *eap_handler_alloc(rlm_eap_t *inst) /* Doesn't need to be inside the critical region */ talloc_set_destructor(handler, _eap_handler_free); + if (!inst->dedup_tree) return handler; + + if (radius_xlat(buffer, sizeof(buffer), request, inst->dedup_key, NULL, NULL) < 0) return handler; + + handler->dedup = talloc_strdup(handler, buffer); + + /* + * Delete any old handler + */ + PTHREAD_MUTEX_LOCK(&(inst->session_mutex)); + old = rbtree_finddata(inst->dedup_tree, handler); + if (old) (void) eaplist_delete(inst, request, old, "Cancelling", true); + PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex)); + return handler; } @@ -172,21 +185,24 @@ static uint32_t eap_rand(fr_randctx *ctx) static eap_handler_t *eaplist_delete(rlm_eap_t *inst, REQUEST *request, - eap_handler_t *handler) + eap_handler_t *handler, char const *msg, bool delete) { rbnode_t *node; + if (delete && inst->dedup_tree) (void) rbtree_deletebydata(inst->dedup_tree, handler); + node = rbtree_find(inst->session_tree, handler); if (!node) return NULL; handler = rbtree_node2data(inst->session_tree, node); - RDEBUG("Finished EAP session with state " - "0x%02x%02x%02x%02x%02x%02x%02x%02x", + RDEBUG("%s EAP session with state " + "0x%02x%02x%02x%02x%02x%02x%02x%02x", msg, handler->state[0], handler->state[1], handler->state[2], handler->state[3], handler->state[4], handler->state[5], handler->state[6], handler->state[7]); + /* * Delete old handler from the tree. */ @@ -207,7 +223,28 @@ static eap_handler_t *eaplist_delete(rlm_eap_t *inst, REQUEST *request, } handler->prev = handler->next = NULL; - return handler; + if (!delete) return handler; + + +#ifdef WITH_TLS + /* + * Remove expired TLS sessions. + */ + switch (handler->type) { + case PW_EAP_TLS: + case PW_EAP_TTLS: + case PW_EAP_PEAP: + case PW_EAP_FAST: + tls_fail(handler->opaque); /* MUST be a tls_session! */ + break; + + default: + break; + } +#endif + + talloc_free(handler); + return NULL; } @@ -227,52 +264,12 @@ static void eaplist_expire(rlm_eap_t *inst, REQUEST *request, time_t timestamp) handler = inst->session_head; if (!handler) break; - RDEBUG("Expiring EAP session with state " - "0x%02x%02x%02x%02x%02x%02x%02x%02x", - handler->state[0], handler->state[1], - handler->state[2], handler->state[3], - handler->state[4], handler->state[5], - handler->state[6], handler->state[7]); - /* * Expire entries from the start of the list. * They should be the oldest ones. */ if ((timestamp - handler->timestamp) > (int)inst->timer_limit) { - rbnode_t *node; - node = rbtree_find(inst->session_tree, handler); - rad_assert(node != NULL); - rbtree_delete(inst->session_tree, node); - - /* - * handler == inst->session_head - */ - inst->session_head = handler->next; - if (handler->next) { - handler->next->prev = NULL; - } else { - inst->session_head = NULL; - inst->session_tail = NULL; - } - -#ifdef WITH_TLS - /* - * Remove expired TLS sessions. - */ - switch (handler->type) { - case PW_EAP_TLS: - case PW_EAP_TTLS: - case PW_EAP_PEAP: - case PW_EAP_FAST: - tls_fail(handler->opaque); /* MUST be a tls_session! */ - break; - - default: - break; - } -#endif - - talloc_free(handler); + (void) eaplist_delete(inst, request, handler, "Expiring", true); } else { break; } @@ -347,7 +344,9 @@ int eaplist_add(rlm_eap_t *inst, eap_handler_t *handler) handler->state[4] = handler->trips ^ handler->state[0]; handler->state[5] = handler->eap_id ^ handler->state[1]; handler->state[6] = handler->type ^ handler->state[2]; - handler->state[12] = handler->state[2] ^ (RADIUSD_VERSION & 0xff); + handler->state[8] = handler->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff); + handler->state[10] = handler->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff); + handler->state[12] = handler->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff); fr_pair_value_memcpy(state, handler->state, sizeof(handler->state)); @@ -452,7 +451,7 @@ eap_handler_t *eaplist_find(rlm_eap_t *inst, REQUEST *request, eaplist_expire(inst, request, request->timestamp); - handler = eaplist_delete(inst, request, &myHandler); + handler = eaplist_delete(inst, request, &myHandler, "Removing", false); PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex)); /* diff --git a/src/modules/rlm_eap/rlm_eap.c b/src/modules/rlm_eap/rlm_eap.c index efb9660..18eb1c4 100644 --- a/src/modules/rlm_eap/rlm_eap.c +++ b/src/modules/rlm_eap/rlm_eap.c @@ -38,7 +38,9 @@ static const CONF_PARSER module_config[] = { { "max_eap_type", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_eap_t, max_eap_type), "52" }, { "ignore_unknown_eap_types", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_t, ignore_unknown_types), "no" }, { "cisco_accounting_username_bug", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_t, mod_accounting_username_bug), "no" }, + { "allow_empty_identities", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_t, allow_empty_identities), NULL }, { "max_sessions", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_eap_t, max_sessions), "2048" }, + { "dedup_key", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_t, dedup_key), "" }, CONF_PARSER_TERMINATOR }; @@ -98,6 +100,21 @@ static int eap_handler_cmp(void const *a, void const *b) return 0; } +/* + * Compare two handlers by dedup key + */ +static int CC_HINT(nonnull) dedup_cmp(void const *a, void const *b) +{ + eap_handler_t const *one = a; + eap_handler_t const *two = b; + + if (!one->dedup && two->dedup) return -1; + if (one->dedup && !two->dedup) return +1; + + if (!one->dedup && !two->dedup) return 0; + + return strcmp(one->dedup, two->dedup); +} /* * read the config section and load all the eap authentication types present. @@ -185,6 +202,7 @@ static int mod_instantiate(CONF_SECTION *cs, void *instance) case PW_EAP_TTLS: case PW_EAP_PEAP: case PW_EAP_PWD: + case PW_EAP_TEAP: WARN("rlm_eap (%s): Ignoring EAP method %s because we don't have OpenSSL support", inst->xlat_name, name); continue; @@ -255,6 +273,16 @@ static int mod_instantiate(CONF_SECTION *cs, void *instance) } #endif + if (!inst->dedup_key || !*inst->dedup_key) { + return 0; + } + + inst->dedup_tree = rbtree_create(NULL, dedup_cmp, NULL, 0); + if (!inst->dedup_tree) { + ERROR("rlm_eap (%s): Cannot initialize dedup tree", inst->xlat_name); + return -1; + } + return 0; } @@ -560,19 +588,16 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *reque #ifdef WITH_PROXY -static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(void *instance, REQUEST *request) +static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(UNUSED void *instance, REQUEST *request) { VALUE_PAIR *vp; - size_t length; - rlm_eap_t *inst = instance; + size_t length, eap_length; vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (!vp) return RLM_MODULE_NOOP; - if (vp->vp_length < 4) return RLM_MODULE_NOOP; - - if ((vp->vp_octets[0] == 0) ||( vp->vp_octets[0] > 6)) { - RDEBUG("EAP header byte zero has invalid value"); + if (vp->vp_length < 4) { + RDEBUG("EAP packet is too small"); add_error_cause: /* @@ -582,21 +607,20 @@ static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(void *instance, REQUEST *reque return RLM_MODULE_REJECT; } + /* + * The length field has to match the length of all EAP-Messages. + */ length = (vp->vp_octets[2] << 8) | vp->vp_octets[3]; - if (length != vp->vp_length) { - RDEBUG("EAP length does not match attribute length"); - return RLM_MODULE_REJECT; - } - if (vp->vp_octets[0] != PW_EAP_REQUEST) return RLM_MODULE_NOOP; - if (!inst->max_eap_type) return RLM_MODULE_NOOP; - - if (vp->vp_length < 5) return RLM_MODULE_NOOP; - - if (vp->vp_octets[4] == 254) return RLM_MODULE_NOOP; /* allow extended types */ + /* + * Get length of all EAP-Message attributes + */ + for (eap_length = 0; vp != NULL; vp = vp->next) { + eap_length += vp->vp_length; + } - if (vp->vp_octets[4] > inst->max_eap_type) { - RDEBUG("EAP method %u is too large", vp->vp_octets[4]); + if (length != eap_length) { + RDEBUG("EAP length does not match attribute length"); goto add_error_cause; } diff --git a/src/modules/rlm_eap/rlm_eap.h b/src/modules/rlm_eap/rlm_eap.h index 0b9311c..4d5e1fa 100644 --- a/src/modules/rlm_eap/rlm_eap.h +++ b/src/modules/rlm_eap/rlm_eap.h @@ -63,14 +63,19 @@ typedef struct rlm_eap { bool ignore_unknown_types; bool mod_accounting_username_bug; + bool allow_empty_identities; uint32_t max_sessions; + char const *dedup_key; + #ifdef HAVE_PTHREAD_H pthread_mutex_t session_mutex; pthread_mutex_t handler_mutex; #endif + rbtree_t *dedup_tree; + char const *xlat_name; /* no xlat's yet */ fr_randctx rand_pool; } rlm_eap_t; @@ -102,7 +107,7 @@ eap_handler_t *eap_handler(rlm_eap_t *inst, eap_packet_raw_t **eap_msg, REQUEST /* Memory Management */ EAP_DS *eap_ds_alloc(eap_handler_t *handler); -eap_handler_t *eap_handler_alloc(rlm_eap_t *inst); +eap_handler_t *eap_handler_alloc(rlm_eap_t *inst, REQUEST *request); void eap_ds_free(EAP_DS **eap_ds); int eaplist_add(rlm_eap_t *inst, eap_handler_t *handler) CC_HINT(nonnull); eap_handler_t *eaplist_find(rlm_eap_t *inst, REQUEST *request, eap_packet_raw_t *eap_packet); diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c index 093dc86..d1f45b7 100644 --- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c @@ -479,7 +479,7 @@ static int mod_process(void *arg, eap_handler_t *handler) case PW_CODE_ACCESS_CHALLENGE: RDEBUG("Challenge"); tls_handshake_send(request, tls_session); - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); ret = 1; goto done; diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c index a8589ae..efe9b10 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c @@ -591,8 +591,6 @@ static int CC_HINT(nonnull) eappeap_postproxy(eap_handler_t *handler, void *data fprintf(fr_log_fp, "server %s {\n", fake->server); } - fake->reply->code = PW_CODE_ACCESS_ACCEPT; - /* * Perform a post-auth stage, which will get the EAP * handler, too... @@ -622,6 +620,8 @@ static int CC_HINT(nonnull) eappeap_postproxy(eap_handler_t *handler, void *data request->proxy_reply = talloc_steal(request, fake->reply); fake->reply = NULL; + request->proxy->dst_port = 0; /* hacks for state.c lookups */ + /* * And we're done with this request. */ @@ -667,7 +667,7 @@ static int CC_HINT(nonnull) eappeap_postproxy(eap_handler_t *handler, void *data case RLM_MODULE_HANDLED: RDEBUG2("Reply was handled"); - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); request->proxy_reply->code = PW_CODE_ACCESS_CHALLENGE; return 1; diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c index d9f850c..704b86c 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c @@ -233,7 +233,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) * TLS session initialization is over. Now handle TLS * related handshaking or application data. */ - status = eaptls_start(handler->eap_ds, ssn->peap_flag); + status = eaptls_request(handler->eap_ds, ssn, true); if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); } else { @@ -357,7 +357,7 @@ static int mod_process(void *arg, eap_handler_t *handler) goto done; case RLM_MODULE_HANDLED: - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); ret = 1; goto done; diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/.gitignore b/src/modules/rlm_eap/types/rlm_eap_teap/.gitignore new file mode 100644 index 0000000..01a5daa --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/.gitignore @@ -0,0 +1 @@ +all.mk diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/all.mk.in b/src/modules/rlm_eap/types/rlm_eap_teap/all.mk.in new file mode 100644 index 0000000..dfdcd71 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/all.mk.in @@ -0,0 +1,12 @@ +TARGETNAME := @targetname@ + +ifneq "$(OPENSSL_LIBS)" "" +ifneq "$(TARGETNAME)" "" +TARGET := $(TARGETNAME).a +endif +endif + +SOURCES := $(TARGETNAME).c eap_teap.c eap_teap_crypto.c + +SRC_INCDIRS := ../../ ../../libeap/ +TGT_PREREQS := libfreeradius-eap.a diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/configure b/src/modules/rlm_eap/types/rlm_eap_teap/configure new file mode 100755 index 0000000..e37094d --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/configure @@ -0,0 +1,4512 @@ +#! /bin/sh +# From configure.ac Revision. +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 </dev/null +exec 6>&1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= +PACKAGE_URL= + +ac_unique_file="rlm_eap_teap.c" +ac_subst_vars='LTLIBOBJS +LIBOBJS +mod_cflags +mod_ldflags +targetname +EGREP +GREP +CPP +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +with_rlm_eap_teap +with_openssl_lib_dir +with_openssl_include_dir +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --without-rlm_eap_teap build without rlm_eap_teap + --with-openssl-lib-dir=DIR + directory for LDAP library files + -with-openssl-include-dir=DIR + directory for LDAP include files + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a + nonstandard directory <lib dir> + LIBS libraries to pass to the linker, e.g. -l<library> + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if + you have headers in a nonstandard directory <include dir> + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +echo +echo Running tests for rlm_eap_teap +echo + + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + + + + + +# Check whether --with-rlm_eap_teap was given. +if test "${with_rlm_eap_teap+set}" = set; then : + withval=$with_rlm_eap_teap; +fi + + + +mod_ldflags= +mod_cflags= + + +fail= +fr_status= +fr_features= +: > "config.report" +: > "config.report.tmp" + + + +if test x"$with_rlm_eap_teap" != xno; then + + +openssl_lib_dir= + +# Check whether --with-openssl-lib-dir was given. +if test "${with_openssl_lib_dir+set}" = set; then : + withval=$with_openssl_lib_dir; case "$withval" in + no) + as_fn_error $? "Need openssl-lib-dir" "$LINENO" 5 + ;; + yes) + ;; + *) + openssl_lib_dir="$withval" + ;; + esac +fi + + +openssl_include_dir= + +# Check whether --with-openssl-include-dir was given. +if test "${with_openssl_include_dir+set}" = set; then : + withval=$with_openssl_include_dir; case "$withval" in + no) + as_fn_error $? "Need openssl-include-dir" "$LINENO" 5 + ;; + yes) + ;; + *) + openssl_include_dir="$withval" + ;; + esac +fi + + + +smart_try_dir=$openssl_include_dir +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdio.h> +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdarg.h> +#include <stdio.h> +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + +ac_safe=`echo "openssl/ec.h" | sed 'y%./+-%__pm%'` +old_CPPFLAGS="$CPPFLAGS" +smart_include= +smart_include_dir="/usr/local/include /opt/include" + +_smart_try_dir= +_smart_include_dir= + +for _prefix in $smart_prefix ""; do + for _dir in $smart_try_dir; do + _smart_try_dir="${_smart_try_dir} ${_dir}/${_prefix}" + done + + for _dir in $smart_include_dir; do + _smart_include_dir="${_smart_include_dir} ${_dir}/${_prefix}" + done +done + +if test "x$_smart_try_dir" != "x"; then + for try in $_smart_try_dir; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl/ec.h in $try" >&5 +$as_echo_n "checking for openssl/ec.h in $try... " >&6; } + CPPFLAGS="-isystem $try $old_CPPFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include <openssl/ec.h> +int +main () +{ +int a = 1; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + smart_include="-isystem $try" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + +else + + smart_include= + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done + CPPFLAGS="$old_CPPFLAGS" +fi + +if test "x$smart_include" = "x"; then + for _prefix in $smart_prefix; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${_prefix}/openssl/ec.h" >&5 +$as_echo_n "checking for ${_prefix}/openssl/ec.h... " >&6; } + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include <openssl/ec.h> +int +main () +{ +int a = 1; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + smart_include="-isystem ${_prefix}/" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + +else + + smart_include= + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done +fi + +if test "x$smart_include" = "x"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl/ec.h" >&5 +$as_echo_n "checking for openssl/ec.h... " >&6; } + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include <openssl/ec.h> +int +main () +{ +int a = 1; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + smart_include=" " + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + +else + + smart_include= + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +if test "x$smart_include" = "x"; then + + for try in $_smart_include_dir; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl/ec.h in $try" >&5 +$as_echo_n "checking for openssl/ec.h in $try... " >&6; } + CPPFLAGS="-isystem $try $old_CPPFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include <openssl/ec.h> +int +main () +{ +int a = 1; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + smart_include="-isystem $try" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + +else + + smart_include= + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + done + CPPFLAGS="$old_CPPFLAGS" +fi + +if test "x$smart_include" != "x"; then + eval "ac_cv_header_$ac_safe=yes" + CPPFLAGS="$smart_include $old_CPPFLAGS" + SMART_CPPFLAGS="$smart_include $SMART_CPPFLAGS" +fi + +smart_prefix= + +if test "$ac_cv_header_openssl_ec_h" != "yes"; then + +fail="$fail openssl/ec.h" + +fi + +smart_try_dir=$openssl_lib_dir + + +sm_lib_safe=`echo "crypto" | sed 'y%./+-%__p_%'` +sm_func_safe=`echo "EVP_CIPHER_CTX_new" | sed 'y%./+-%__p_%'` + +old_LIBS="$LIBS" +old_CPPFLAGS="$CPPFLAGS" +smart_lib= +smart_ldflags= +smart_lib_dir= + +if test "x$smart_try_dir" != "x"; then + for try in $smart_try_dir; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for EVP_CIPHER_CTX_new in -lcrypto in $try" >&5 +$as_echo_n "checking for EVP_CIPHER_CTX_new in -lcrypto in $try... " >&6; } + LIBS="-lcrypto $old_LIBS" + CPPFLAGS="-L$try -Wl,-rpath,$try $old_CPPFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +extern char EVP_CIPHER_CTX_new(); +int +main () +{ +EVP_CIPHER_CTX_new() + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + smart_lib="-lcrypto" + smart_ldflags="-L$try -Wl,-rpath,$try" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + done + LIBS="$old_LIBS" + CPPFLAGS="$old_CPPFLAGS" +fi + +if test "x$smart_lib" = "x"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for EVP_CIPHER_CTX_new in -lcrypto" >&5 +$as_echo_n "checking for EVP_CIPHER_CTX_new in -lcrypto... " >&6; } + LIBS="-lcrypto $old_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +extern char EVP_CIPHER_CTX_new(); +int +main () +{ +EVP_CIPHER_CTX_new() + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + smart_lib="-lcrypto" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LIBS="$old_LIBS" +fi + +if test "x$smart_lib" = "x"; then + for try in /usr/local/lib /opt/lib; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for EVP_CIPHER_CTX_new in -lcrypto in $try" >&5 +$as_echo_n "checking for EVP_CIPHER_CTX_new in -lcrypto in $try... " >&6; } + LIBS="-lcrypto $old_LIBS" + CPPFLAGS="-L$try -Wl,-rpath,$try $old_CPPFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +extern char EVP_CIPHER_CTX_new(); +int +main () +{ +EVP_CIPHER_CTX_new() + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + smart_lib="-lcrypto" + smart_ldflags="-L$try -Wl,-rpath,$try" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + done + LIBS="$old_LIBS" + CPPFLAGS="$old_CPPFLAGS" +fi + +if test "x$smart_lib" != "x"; then + eval "ac_cv_lib_${sm_lib_safe}_${sm_func_safe}=yes" + LIBS="$smart_ldflags $smart_lib $old_LIBS" + SMART_LIBS="$smart_ldflags $smart_lib $SMART_LIBS" +fi + +if test "x$ac_cv_lib_crypto_EVP_CIPHER_CTX_new" != "xyes"; then + +fail="$fail libssl" + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + # <limits.h> exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ac_nonexistent.h> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since + # <limits.h> exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include <limits.h> +#else +# include <assert.h> +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <ac_nonexistent.h> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <openssl/crypto.h> + #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) + yes + #endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "yes" >/dev/null 2>&1; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OpenSSL version >= 1.1.1" >&5 +$as_echo_n "checking for OpenSSL version >= 1.1.1... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OpenSSL version >= 1.1.1" >&5 +$as_echo_n "checking for OpenSSL version >= 1.1.1... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fail="$fail OpenSSL>=1.1.1" + + + +fi +rm -f conftest* + + + + targetname=rlm_eap_teap +else + targetname= + echo \*\*\* module rlm_eap_teap is disabled. + + +fr_status="disabled" + +fi + +if test x"$fail" != x""; then + targetname="" + + + if test x"${enable_strict_dependencies}" = x"yes"; then + as_fn_error $? "set --without-rlm_eap_teap to disable it explicitly." "$LINENO" 5 + else + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: silently not building rlm_eap_teap." >&5 +$as_echo "$as_me: WARNING: silently not building rlm_eap_teap." >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: FAILURE: rlm_eap_teap requires: $fail." >&5 +$as_echo "$as_me: WARNING: FAILURE: rlm_eap_teap requires: $fail." >&2;}; + fail="$(echo $fail)" + + +fr_status="skipping (requires $fail)" + + fr_features= + + fi + +else + + +fr_status="OK" + +fi + +if test x"$fr_features" = x""; then + $as_echo "$fr_status" > "config.report" +else + $as_echo_n "$fr_status ... " > "config.report" + cat "config.report.tmp" >> "config.report" +fi + +rm "config.report.tmp" + + + + + + + +ac_config_files="$ac_config_files all.mk" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "all.mk") CONFIG_FILES="$CONFIG_FILES all.mk" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' <conf$$subs.awk | sed ' +/^[^""]/{ + N + s/\n// +} +' >>$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/configure.ac b/src/modules/rlm_eap/types/rlm_eap_teap/configure.ac new file mode 100644 index 0000000..6247f4c --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/configure.ac @@ -0,0 +1,86 @@ +AC_PREREQ([2.69]) +AC_INIT +AC_CONFIG_SRCDIR([rlm_eap_teap.c]) +AC_REVISION($Revision$) +FR_INIT_MODULE([rlm_eap_teap]) + +mod_ldflags= +mod_cflags= + +FR_MODULE_START_TESTS + +dnl ############################################################ +dnl # Check for command line options +dnl ############################################################ +dnl extra argument: --with-openssl-lib-dir +openssl_lib_dir= +AC_ARG_WITH(openssl-lib-dir, + [AS_HELP_STRING([--with-openssl-lib-dir=DIR], + [directory for LDAP library files])], + [case "$withval" in + no) + AC_MSG_ERROR(Need openssl-lib-dir) + ;; + yes) + ;; + *) + openssl_lib_dir="$withval" + ;; + esac]) + +dnl extra argument: --with-openssl-include-dir +openssl_include_dir= +AC_ARG_WITH(openssl-include-dir, + [AS_HELP_STRING([-with-openssl-include-dir=DIR], + [directory for LDAP include files])], + [case "$withval" in + no) + AC_MSG_ERROR(Need openssl-include-dir) + ;; + yes) + ;; + *) + openssl_include_dir="$withval" + ;; + esac]) + +dnl ############################################################ +dnl # Check for header files +dnl ############################################################ + +smart_try_dir=$openssl_include_dir +FR_SMART_CHECK_INCLUDE(openssl/ec.h) +if test "$ac_cv_header_openssl_ec_h" != "yes"; then + FR_MODULE_FAIL([openssl/ec.h]) +fi + +smart_try_dir=$openssl_lib_dir +FR_SMART_CHECK_LIB(crypto, EVP_CIPHER_CTX_new) +if test "x$ac_cv_lib_crypto_EVP_CIPHER_CTX_new" != "xyes"; then + FR_MODULE_FAIL([libssl]) +fi + +AC_EGREP_CPP(yes, + [#include <openssl/crypto.h> + #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) + yes + #endif + ], + [ + AC_MSG_CHECKING([for OpenSSL version >= 1.1.1]) + AC_MSG_RESULT(yes) + ], + [ + AC_MSG_CHECKING([for OpenSSL version >= 1.1.1]) + AC_MSG_RESULT(no) + FR_MODULE_FAIL([OpenSSL>=1.1.1]) + ] +) + +FR_MODULE_END_TESTS + +AC_SUBST(mod_ldflags) +AC_SUBST(mod_cflags) + +AC_CONFIG_FILES([all.mk]) +AC_OUTPUT diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap.c b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap.c new file mode 100644 index 0000000..dbe694e --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap.c @@ -0,0 +1,1488 @@ +/* + * eap_teap.c contains the interfaces that are called from the main handler + * + * Version: $Id$ + * + * Copyright (C) 2022 Network RADIUS SARL <legal@networkradius.com> + * + * This software may not be redistributed in any form without the prior + * written consent of Network RADIUS. + * + * 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. + */ + +RCSID("$Id$") + +#include "eap_teap.h" +#include "eap_teap_crypto.h" +#include <freeradius-devel/sha1.h> +#include <openssl/ssl.h> +#include <openssl/rand.h> + +#define PW_EAP_TEAP_TLV_IDENTITY (PW_FREERADIUS_EAP_TEAP_TLV | (EAP_TEAP_TLV_IDENTITY << 8)) +#define PW_EAP_TEAP_TLV_PAC (PW_FREERADIUS_EAP_TEAP_TLV | (EAP_TEAP_TLV_PAC << 8)) + +#define EAPTLS_MPPE_KEY_LEN 32 + +#define RDEBUGHEX(_label, _data, _length) \ +do {\ + char __buf[8192];\ + for (size_t i = 0; (i < (size_t) _length) && (3*i < sizeof(__buf)); i++) {\ + sprintf(&__buf[3*i], " %02x", (uint8_t)(_data)[i]);\ + }\ + RDEBUG("%s - hexdump(len=%zu):%s", _label, (size_t)_length, __buf);\ +} while (0) + +#define RANDFILL(x) do { rad_assert(sizeof(x) % sizeof(uint32_t) == 0); for (size_t i = 0; i < sizeof(x); i += sizeof(uint32_t)) *((uint32_t *)&x[i]) = fr_rand(); } while(0) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) +#define MIN(a,b) (((a)>(b)) ? (b) : (a)) + +struct crypto_binding_buffer { + uint16_t tlv_type; + uint16_t length; + eap_tlv_crypto_binding_tlv_t binding; + uint8_t eap_type; + uint8_t outer_tlvs[1]; +} CC_HINT(__packed__); +#define CRYPTO_BINDING_BUFFER_INIT(_cbb) \ +do {\ + _cbb->tlv_type = htons(EAP_TEAP_TLV_MANDATORY | EAP_TEAP_TLV_CRYPTO_BINDING);\ + _cbb->length = htons(sizeof(struct eap_tlv_crypto_binding_tlv_t));\ + _cbb->eap_type = PW_EAP_TEAP;\ +} while (0) + +static struct teap_imck_t imck_zeros = { }; + +/** + * RFC 7170 EAP-TEAP Authentication Phase 1: Key Derivations + */ +static void eap_teap_init_keys(REQUEST *request, tls_session_t *tls_session) +{ + teap_tunnel_t *t = tls_session->opaque; + + const EVP_MD *md = SSL_CIPHER_get_handshake_digest(SSL_get_current_cipher(tls_session->ssl)); + const int md_type = EVP_MD_type(md); + + RDEBUG("Using MAC %s (%d)", OBJ_nid2sn(md_type), md_type); + + RDEBUG2("Deriving EAP-TEAP keys"); + + rad_assert(t->received_version > -1); + rad_assert(t->imckc == 0); + + /* S-IMCK[0] = session_key_seed (RFC7170, Section 5.1) */ + eaptls_gen_keys_only(request, tls_session->ssl, "EXPORTER: teap session key seed", NULL, 0, t->imck_msk.simck, sizeof(t->imck_msk.simck)); + memcpy(t->imck_emsk.simck, t->imck_msk.simck, sizeof(t->imck_msk.simck)); + RDEBUGHEX("S-IMCK[0]", t->imck_msk.simck, sizeof(t->imck_msk.simck)); +} + +/** + * RFC 7170 EAP-TEAP Intermediate Compound Key Derivations - Section 5.2 + */ +/** + * RFC 7170 - Intermediate Compound Key Derivations + */ +static void eap_teap_derive_imck(REQUEST *request, tls_session_t *tls_session, + uint8_t *msk, size_t msklen, + uint8_t *emsk, size_t emsklen) +{ + teap_tunnel_t *t = tls_session->opaque; + + t->imckc++; + RDEBUG2("Updating ICMK (j = %d)", t->imckc); + + uint8_t imsk_msk[EAP_TEAP_IMSK_LEN] = {0}; + uint8_t imsk_emsk[EAP_TEAP_IMSK_LEN + 32]; // +32 for EMSK overflow + struct teap_imck_t imck_msk, imck_emsk; + + uint8_t imck_label[27] = "Inner Methods Compound Keys"; // width trims trailing \0 + struct iovec imck_seed[2] = { + { (void *)imck_label, sizeof(imck_label) }, + { NULL, EAP_TEAP_IMSK_LEN } + }; + + if (msklen) { + memcpy(imsk_msk, msk, MIN(msklen, EAP_TEAP_IMSK_LEN)); + RDEBUGHEX("IMSK from MSK", imsk_msk, EAP_TEAP_IMSK_LEN); + } else { + RDEBUGHEX("IMSK Zero", imsk_msk, EAP_TEAP_IMSK_LEN); + } + imck_seed[1].iov_base = imsk_msk; + TLS_PRF(tls_session->ssl, + t->imck_msk.simck, sizeof(t->imck_msk.simck), + imck_seed, ARRAY_SIZE(imck_seed), + (uint8_t *)&imck_msk, sizeof(imck_msk)); + + /* IMCK[j] 60 octets => S-IMCK[j] first 40 octets, CMK[j] last 20 octets */ + RDEBUGHEX("MSK S-IMCK[j]", imck_msk.simck, sizeof(imck_msk.simck)); + RDEBUGHEX("MSK CMK[j]", imck_msk.cmk, sizeof(imck_msk.cmk)); + + if (emsklen) { + uint8_t emsk_label[20] = "TEAPbindkey@ietf.org"; + uint8_t null[1] = {0}; + uint8_t length[2] = {0,64}; /* length of 64 bytes in two bytes in network order */ + struct iovec emsk_seed[] = { + { (void *)emsk_label, sizeof(emsk_label) }, + { (void *)null, sizeof(null) }, + { (void *)length, sizeof(length) } + }; + + TLS_PRF(tls_session->ssl, + emsk, emsklen, + emsk_seed, ARRAY_SIZE(emsk_seed), + imsk_emsk, sizeof(imsk_emsk)); + + RDEBUGHEX("IMSK from EMSK", imsk_emsk, EAP_TEAP_IMSK_LEN); + + imck_seed[1].iov_base = imsk_emsk; + TLS_PRF(tls_session->ssl, + t->imck_emsk.simck, sizeof(t->imck_emsk.simck), + imck_seed, ARRAY_SIZE(imck_seed), + (uint8_t *)&imck_emsk, sizeof(imck_emsk)); + + /* IMCK[j] 60 octets => S-IMCK[j] first 40 octets, CMK[j] last 20 octets */ + RDEBUGHEX("EMSK S-IMCK[j]", imck_emsk.simck, sizeof(imck_emsk.simck)); + RDEBUGHEX("EMSK CMK[j]", imck_emsk.cmk, sizeof(imck_emsk.cmk)); + } + + memcpy(&t->imck_msk, &imck_msk, sizeof(imck_msk)); + if (emsklen) memcpy(&t->imck_emsk, &imck_emsk, sizeof(imck_emsk)); +} + +static void eap_teap_tlv_append(tls_session_t *tls_session, int tlv, bool mandatory, int length, const void *data) +{ + uint16_t hdr[2]; + + hdr[0] = htons(tlv | (mandatory ? EAP_TEAP_TLV_MANDATORY : 0)); + hdr[1] = htons(length); + + tls_session->record_plus(&tls_session->clean_in, &hdr, 4); + tls_session->record_plus(&tls_session->clean_in, data, length); +} + +static void eap_teap_send_error(tls_session_t *tls_session, int error) +{ + uint32_t value; + value = htonl(error); + + eap_teap_tlv_append(tls_session, EAP_TEAP_TLV_ERROR, true, sizeof(value), &value); +} + +static void eap_teap_append_identity(tls_session_t *tls_session, int value) { + uint16_t identity; + identity = htons(value); + + eap_teap_tlv_append(tls_session, EAP_TEAP_TLV_IDENTITY, false, sizeof(identity), &identity); +} + +static void eap_teap_append_result(tls_session_t *tls_session, PW_CODE code) +{ + teap_tunnel_t *t = (teap_tunnel_t *) tls_session->opaque; + + int type = (t->result_final) + ? EAP_TEAP_TLV_RESULT + : EAP_TEAP_TLV_INTERMED_RESULT; + + uint16_t state = (code == PW_CODE_ACCESS_REJECT) + ? EAP_TEAP_TLV_RESULT_FAILURE + : EAP_TEAP_TLV_RESULT_SUCCESS; + state = htons(state); + + eap_teap_tlv_append(tls_session, type, true, sizeof(state), &state); +} + +static void eap_teap_append_eap_identity_request(REQUEST *request, tls_session_t *tls_session, eap_handler_t *eap_session) +{ + eap_packet_raw_t eap_packet; + + RDEBUG("Sending EAP-Identity"); + + eap_packet.code = PW_EAP_REQUEST; + eap_packet.id = eap_session->eap_ds->response->id + 1; + eap_packet.length[0] = 0; + eap_packet.length[1] = EAP_HEADER_LEN + 1; + eap_packet.data[0] = PW_EAP_IDENTITY; + + eap_teap_tlv_append(tls_session, EAP_TEAP_TLV_EAP_PAYLOAD, true, sizeof(eap_packet), &eap_packet); +} + +#if 0 +static void eap_teap_send_pac_tunnel(REQUEST *request, tls_session_t *tls_session) +{ + teap_tunnel_t *t = tls_session->opaque; + eap_teap_pac_t pac; + eap_teap_attr_pac_opaque_plaintext_t opaque_plaintext; + int alen, dlen; + + memset(&pac, 0, sizeof(pac)); + memset(&opaque_plaintext, 0, sizeof(opaque_plaintext)); + + RDEBUG("Sending Tunnel PAC"); + + pac.key.hdr.type = htons(EAP_TEAP_TLV_MANDATORY | PAC_INFO_PAC_KEY); + pac.key.hdr.length = htons(sizeof(pac.key.data)); + rad_assert(sizeof(pac.key.data) % sizeof(uint32_t) == 0); + RANDFILL(pac.key.data); + + pac.info.lifetime.hdr.type = htons(PAC_INFO_PAC_LIFETIME); + pac.info.lifetime.hdr.length = htons(sizeof(pac.info.lifetime.data)); + pac.info.lifetime.data = htonl(time(NULL) + t->pac_lifetime); + + pac.info.a_id.hdr.type = htons(EAP_TEAP_TLV_MANDATORY | PAC_INFO_A_ID); + pac.info.a_id.hdr.length = htons(sizeof(pac.info.a_id.data)); + memcpy(pac.info.a_id.data, t->a_id, sizeof(pac.info.a_id.data)); + + pac.info.a_id_info.hdr.type = htons(PAC_INFO_A_ID_INFO); + pac.info.a_id_info.hdr.length = htons(sizeof(pac.info.a_id_info.data)); + #define MIN(a,b) (((a)>(b)) ? (b) : (a)) + alen = MIN(talloc_array_length(t->authority_identity) - 1, sizeof(pac.info.a_id_info.data)); + memcpy(pac.info.a_id_info.data, t->authority_identity, alen); + + pac.info.type.hdr.type = htons(EAP_TEAP_TLV_MANDATORY | PAC_INFO_PAC_TYPE); + pac.info.type.hdr.length = htons(sizeof(pac.info.type.data)); + pac.info.type.data = htons(PAC_TYPE_TUNNEL); + + pac.info.hdr.type = htons(EAP_TEAP_TLV_MANDATORY | PAC_INFO_PAC_INFO); + pac.info.hdr.length = htons(sizeof(pac.info.lifetime) + + sizeof(pac.info.a_id) + + sizeof(pac.info.a_id_info) + + sizeof(pac.info.type)); + + memcpy(&opaque_plaintext.type, &pac.info.type, sizeof(opaque_plaintext.type)); + memcpy(&opaque_plaintext.lifetime, &pac.info.lifetime, sizeof(opaque_plaintext.lifetime)); + memcpy(&opaque_plaintext.key, &pac.key, sizeof(opaque_plaintext.key)); + + + rad_assert(PAC_A_ID_LENGTH <= EVP_GCM_TLS_TAG_LEN); + memcpy(pac.opaque.aad, t->a_id, PAC_A_ID_LENGTH); + rad_assert(RAND_bytes(pac.opaque.iv, sizeof(pac.opaque.iv)) != 0); + dlen = eap_teap_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext), + t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv, + pac.opaque.data, pac.opaque.tag); + if (dlen < 0) return; + + pac.opaque.hdr.type = htons(EAP_TEAP_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE); + pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen); + + eap_teap_tlv_append(tls_session, EAP_TEAP_TLV_MANDATORY | EAP_TEAP_TLV_PAC, true, + sizeof(pac) - sizeof(pac.opaque.data) + dlen, &pac); +} +#endif + +/* + * RFC7170 and the consequences of EID5768, EID5770 and EID5775 makes the path forward unclear, + * so just do what hostapd does...which the IETF probably agree with anyway: + * https://mailarchive.ietf.org/arch/msg/emu/mXzpSGEn86Zx_fa4f1uULYMhMoM/ + */ +static void eap_teap_append_crypto_binding(REQUEST *request, tls_session_t *tls_session, + uint8_t *msk, size_t msklen, + uint8_t *emsk, size_t emsklen) +{ + teap_tunnel_t *t = tls_session->opaque; + uint8_t mac_msk[EVP_MAX_MD_SIZE], mac_emsk[EVP_MAX_MD_SIZE]; + unsigned int maclen = EVP_MAX_MD_SIZE; + uint8_t *buf; + unsigned int olen; + struct crypto_binding_buffer *cbb; + + RDEBUG("Sending Cryptobinding"); + + eap_teap_derive_imck(request, tls_session, msk, msklen, emsk, emsklen); + + t->imck_emsk_available = emsklen > 0; + + olen = tls_session->outer_tlvs_octets ? talloc_array_length(tls_session->outer_tlvs_octets) : 0; + + buf = talloc_zero_array(request, uint8_t, sizeof(struct crypto_binding_buffer) - 1/*outer_tlvs*/ + olen); + rad_assert(buf != NULL); + + cbb = (struct crypto_binding_buffer *)buf; + + CRYPTO_BINDING_BUFFER_INIT(cbb); + cbb->binding.version = EAP_TEAP_VERSION; + cbb->binding.received_version = t->received_version; + + cbb->binding.subtype = ((emsklen ? EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_BOTH : EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_MSK) << 4) | EAP_TEAP_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST; + + rad_assert(sizeof(cbb->binding.nonce) % sizeof(uint32_t) == 0); + RANDFILL(cbb->binding.nonce); + cbb->binding.nonce[sizeof(cbb->binding.nonce) - 1] &= ~0x01; /* RFC 7170, Section 4.2.13 */ + + if (olen) memcpy(cbb->outer_tlvs, tls_session->outer_tlvs_octets, olen); + + RDEBUGHEX("BUFFER for Compound MAC calculation", buf, talloc_array_length(buf)); + + const EVP_MD *md = SSL_CIPHER_get_handshake_digest(SSL_get_current_cipher(tls_session->ssl)); + HMAC(md, &t->imck_msk.cmk, EAP_TEAP_CMK_LEN, buf, talloc_array_length(buf), mac_msk, &maclen); + if (t->imck_emsk_available) { + HMAC(md, &t->imck_emsk.cmk, EAP_TEAP_CMK_LEN, buf, talloc_array_length(buf), mac_emsk, &maclen); + } + memcpy(cbb->binding.msk_compound_mac, &mac_msk, sizeof(cbb->binding.msk_compound_mac)); + if (t->imck_emsk_available) { + memcpy(cbb->binding.emsk_compound_mac, &mac_emsk, sizeof(cbb->binding.emsk_compound_mac)); + } + + eap_teap_tlv_append(tls_session, EAP_TEAP_TLV_CRYPTO_BINDING, true, sizeof(cbb->binding), (uint8_t *)&cbb->binding); +} + +static int eap_teap_verify(REQUEST *request, tls_session_t *tls_session, uint8_t const *data, unsigned int data_len) +{ + uint16_t attr; + uint16_t length; + unsigned int remaining = data_len; + int total = 0; + int num[EAP_TEAP_TLV_MAX] = {0}; + teap_tunnel_t *t = (teap_tunnel_t *) tls_session->opaque; + uint32_t present = 0; + uint32_t error = 0; + uint16_t status = 0; + + rad_assert(sizeof(present) * 8 > EAP_TEAP_TLV_MAX); + + while (remaining > 0) { + if (remaining < 4) { + RDEBUG2("EAP-TEAP TLV is too small (%u) to contain a EAP-TEAP TLV header", remaining); + return 0; + } + + memcpy(&attr, data, sizeof(attr)); + attr = ntohs(attr) & EAP_TEAP_TLV_TYPE; + + switch (attr) { + case EAP_TEAP_TLV_RESULT: + case EAP_TEAP_TLV_NAK: + case EAP_TEAP_TLV_ERROR: + case EAP_TEAP_TLV_VENDOR_SPECIFIC: + case EAP_TEAP_TLV_EAP_PAYLOAD: + case EAP_TEAP_TLV_INTERMED_RESULT: + case EAP_TEAP_TLV_PAC: + case EAP_TEAP_TLV_CRYPTO_BINDING: + num[attr]++; + present |= 1 << attr; + + if (num[EAP_TEAP_TLV_EAP_PAYLOAD] > 1) { + RDEBUG("Too many EAP-Payload TLVs"); +unexpected: + for (int i = 0; i < EAP_TEAP_TLV_MAX; i++) + if (present & (1 << i)) + RDEBUG(" - attribute %d is present", i); + eap_teap_send_error(tls_session, EAP_TEAP_ERR_UNEXPECTED_TLV); + return 0; + } + + if (num[EAP_TEAP_TLV_INTERMED_RESULT] > 1) { + RDEBUG("Too many Intermediate-Result TLVs"); + goto unexpected; + } + break; + default: + if ((data[0] & 0x80) != 0) { + RDEBUG("Unknown mandatory TLV %02x", attr); + goto unexpected; + } + + num[0]++; + } + + total++; + + memcpy(&length, data + 2, sizeof(length)); + length = ntohs(length); + + data += 4; + remaining -= 4; + + if (length > remaining) { + RDEBUG2("EAP-TEAP TLV %u is longer than room remaining in the packet (%u > %u).", attr, + length, remaining); + return 0; + } + + /* + * If the rest of the TLVs are larger than + * this attribute, continue. + * + * Otherwise, if the attribute over-flows the end + * of the TLCs, die. + */ + if (remaining < length) { + RDEBUG2("EAP-TEAP TLV overflows packet!"); + return 0; + } + + if (attr == EAP_TEAP_TLV_ERROR) { + if (length != 4) goto fail_length; + error = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; + } + + /* + * If there's an error, we bail out of the + * authentication process before allocating + * memory. + */ + if ((attr == EAP_TEAP_TLV_INTERMED_RESULT) || (attr == EAP_TEAP_TLV_RESULT)) { + if (length < 2) { + fail_length: + RDEBUG("EAP-TEAP TLV %u is too short. Expected 2, got %d.", attr, length); + return 0; + } + + status = (data[0] << 8) | data[1]; + if (status == 0) goto unknown_value; + } + + /* + * remaining > length, continue. + */ + remaining -= length; + data += length; + } + + /* + * Check status if we have it. + */ + if (status) { + if (status == EAP_TEAP_TLV_RESULT_FAILURE) { + if (!error) { + RDEBUG("EAP-TEAP TLV Status indicates failure with error %u. Rejecting request.", error); + } else { + RDEBUG("EAP-TEAP TLV Status indicates failure. Rejecting request."); + } + return 0; + } + + if (status != EAP_TEAP_TLV_RESULT_SUCCESS) { + unknown_value: + RDEBUG("EAP-TEAP TLV Status contains unknown value %u. Rejecting request.", status); + goto unexpected; + } + } + + /* + * Check if the peer mixed & matched TLVs. + */ + if ((num[EAP_TEAP_TLV_NAK] > 0) && (num[EAP_TEAP_TLV_NAK] != total)) { + RDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request."); + goto unexpected; + } + + /* + * RFC7170 EID5844 says we can have Intermediate-Result and Result TLVs all in one + */ + + /* + * Check mandatory or not mandatory TLVs. + */ + switch (t->stage) { + case TLS_SESSION_HANDSHAKE: + if (present) { + RDEBUG("Unexpected TLVs in TLS Session Handshake stage"); + goto unexpected; + } + break; + case AUTHENTICATION: + if (present & ~((1 << EAP_TEAP_TLV_EAP_PAYLOAD) | (1 << EAP_TEAP_TLV_CRYPTO_BINDING) | (1 << EAP_TEAP_TLV_INTERMED_RESULT) | (1 << EAP_TEAP_TLV_RESULT))) { + RDEBUG("Unexpected TLVs in authentication stage"); + goto unexpected; + } + break; + case PROVISIONING: + if (present & ~((1 << EAP_TEAP_TLV_PAC) | (1 << EAP_TEAP_TLV_RESULT))) { + RDEBUG("Unexpected TLVs in provisioning stage"); + goto unexpected; + } + break; + case COMPLETE: + if (present) { + RDEBUG("Unexpected TLVs in complete stage"); + goto unexpected; + } + break; + default: + RDEBUG("Unexpected stage %d", t->stage); + return 0; + } + + /* + * We got this far. It looks OK. + */ + return 1; +} + +static ssize_t eap_teap_decode_vp(TALLOC_CTX *request, DICT_ATTR const *parent, + uint8_t const *data, size_t const attr_len, VALUE_PAIR **out) +{ + int8_t tag = TAG_NONE; + VALUE_PAIR *vp; + uint8_t const *p = data; + + /* + * FIXME: Attrlen can be larger than 253 for extended attrs! + */ + if (!parent || !out ) { + RERROR("eap_teap_decode_vp: Invalid arguments"); + return -1; + } + + /* + * Silently ignore zero-length attributes. + */ + if (attr_len == 0) return 0; + + /* + * And now that we've verified the basic type + * information, decode the actual p. + */ + vp = fr_pair_afrom_da(request, parent); + if (!vp) return -1; + + vp->vp_length = attr_len; + vp->tag = tag; + + switch (parent->type) { + case PW_TYPE_STRING: + fr_pair_value_bstrncpy(vp, p, attr_len); + break; + + case PW_TYPE_OCTETS: + fr_pair_value_memcpy(vp, p, attr_len); + break; + + case PW_TYPE_ABINARY: + if (vp->vp_length > sizeof(vp->vp_filter)) { + vp->vp_length = sizeof(vp->vp_filter); + } + memcpy(vp->vp_filter, p, vp->vp_length); + break; + + case PW_TYPE_BYTE: + vp->vp_byte = p[0]; + break; + + case PW_TYPE_SHORT: + vp->vp_short = (p[0] << 8) | p[1]; + break; + + case PW_TYPE_INTEGER: + case PW_TYPE_SIGNED: /* overloaded with vp_integer */ + memcpy(&vp->vp_integer, p, 4); + vp->vp_integer = ntohl(vp->vp_integer); + break; + + case PW_TYPE_INTEGER64: + memcpy(&vp->vp_integer64, p, 8); + vp->vp_integer64 = ntohll(vp->vp_integer64); + break; + + case PW_TYPE_DATE: + memcpy(&vp->vp_date, p, 4); + vp->vp_date = ntohl(vp->vp_date); + break; + + case PW_TYPE_ETHERNET: + memcpy(vp->vp_ether, p, 6); + break; + + case PW_TYPE_IPV4_ADDR: + memcpy(&vp->vp_ipaddr, p, 4); + break; + + case PW_TYPE_IFID: + memcpy(vp->vp_ifid, p, 8); + break; + + case PW_TYPE_IPV6_ADDR: + memcpy(&vp->vp_ipv6addr, p, 16); + break; + + case PW_TYPE_IPV6_PREFIX: + /* + * FIXME: double-check that + * (vp->vp_octets[1] >> 3) matches vp->vp_length + 2 + */ + memcpy(vp->vp_ipv6prefix, p, vp->vp_length); + if (vp->vp_length < 18) { + memset(((uint8_t *)vp->vp_ipv6prefix) + vp->vp_length, 0, + 18 - vp->vp_length); + } + break; + + case PW_TYPE_IPV4_PREFIX: + /* FIXME: do the same double-check as for IPv6Prefix */ + memcpy(vp->vp_ipv4prefix, p, vp->vp_length); + + /* + * /32 means "keep all bits". Otherwise, mask + * them out. + */ + if ((p[1] & 0x3f) > 32) { + uint32_t addr, mask; + + memcpy(&addr, vp->vp_octets + 2, sizeof(addr)); + mask = 1; + mask <<= (32 - (p[1] & 0x3f)); + mask--; + mask = ~mask; + mask = htonl(mask); + addr &= mask; + memcpy(vp->vp_ipv4prefix + 2, &addr, sizeof(addr)); + } + break; + + default: + RERROR("eap_teap_decode_vp: type %d Internal sanity check %d ", parent->type, __LINE__); + fr_pair_list_free(&vp); + return -1; + } + vp->type = VT_DATA; + *out = vp; + return attr_len; +} + + +VALUE_PAIR *eap_teap_teap2vp(REQUEST *request, SSL *ssl, uint8_t const *data, size_t data_len, + DICT_ATTR const *teap_da, vp_cursor_t *out) +{ + uint16_t attr; + uint16_t length; + size_t data_left = data_len; + VALUE_PAIR *first = NULL; + VALUE_PAIR *vp = NULL; + DICT_ATTR const *da; + + if (!teap_da) + teap_da = dict_attrbyvalue(PW_FREERADIUS_EAP_TEAP_TLV, VENDORPEC_FREERADIUS); + rad_assert(teap_da != NULL); + + if (!out) { + out = talloc(request, vp_cursor_t); + rad_assert(out != NULL); + fr_cursor_init(out, &first); + } + + /* + * Decode the TLVs + */ + while (data_left > 0) { + ssize_t decoded; + + /* FIXME do something with mandatory */ + + memcpy(&attr, data, sizeof(attr)); + attr = ntohs(attr) & EAP_TEAP_TLV_TYPE; + + memcpy(&length, data + 2, sizeof(length)); + length = ntohs(length); + + data += 4; + data_left -= 4; + + /* + * Look up the TLV. + * + * For now, if it doesn't exist, ignore it. + */ + da = dict_attrbyparent(teap_da, attr, teap_da->vendor); + if (!da) { + RDEBUG("eap_teap_teap2vp: no sub attribute found %s attr: %u vendor: %u", + teap_da->name, attr, teap_da->vendor); + goto next_attr; + } + if (da->type == PW_TYPE_TLV) { + eap_teap_teap2vp(request, ssl, data, length, da, out); + goto next_attr; + } + decoded = eap_teap_decode_vp(request, da, data, length, &vp); + if (decoded < 0) { + RERROR("Failed decoding %s: %s", da->name, fr_strerror()); + goto next_attr; + } + + fr_cursor_merge(out, vp); + + next_attr: + while (fr_cursor_next(out)) { + /* nothing */ + } + + data += length; + data_left -= length; + } + + /* + * We got this far. It looks OK. + */ + return first; +} + + +static void eapteap_copy_request_to_tunnel(REQUEST *request, REQUEST *fake) { + VALUE_PAIR *copy, *vp; + vp_cursor_t cursor; + + for (vp = fr_cursor_init(&cursor, &request->packet->vps); + vp; + vp = fr_cursor_next(&cursor)) { + /* + * The attribute is a server-side thingy, + * don't copy it. + */ + if ((vp->da->attr > 255) && (((vp->da->attr >> 16) & 0xffff) == 0)) { + continue; + } + + /* + * The outside attribute is already in the + * tunnel, don't copy it. + * + * This works for BOTH attributes which + * are originally in the tunneled request, + * AND attributes which are copied there + * from below. + */ + if (fr_pair_find_by_da(fake->packet->vps, vp->da, TAG_ANY)) continue; + + /* + * Some attributes are handled specially. + */ + if (!vp->da->vendor) switch (vp->da->attr) { + /* + * NEVER copy Message-Authenticator, + * EAP-Message, or State. They're + * only for outside of the tunnel. + */ + case PW_USER_NAME: + case PW_USER_PASSWORD: + case PW_CHAP_PASSWORD: + case PW_CHAP_CHALLENGE: + case PW_PROXY_STATE: + case PW_MESSAGE_AUTHENTICATOR: + case PW_EAP_MESSAGE: + case PW_STATE: + continue; + + /* + * By default, copy it over. + */ + default: + break; + } + + /* + * Don't copy from the head, we've already + * checked it. + */ + copy = fr_pair_list_copy_by_num(fake->packet, vp, vp->da->attr, vp->da->vendor, TAG_ANY); + fr_pair_add(&fake->packet->vps, copy); + } +} + +/* + * Use a reply packet to determine what to do. + */ +static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *eap_session, + tls_session_t *tls_session, + REQUEST *request, RADIUS_PACKET *reply) +{ + rlm_rcode_t rcode = RLM_MODULE_REJECT; + VALUE_PAIR *vp; + vp_cursor_t cursor; + uint8_t msk[2 * CHAP_VALUE_LENGTH] = {0}, emsk[2 * EAPTLS_MPPE_KEY_LEN] = {0}; + size_t msklen = 0, emsklen = 0; + + teap_tunnel_t *t = tls_session->opaque; + + rad_assert(eap_session->request == request); + + /* + * If the response packet was Access-Accept, then + * we're OK. If not, die horribly. + * + * FIXME: EAP-Messages can only start with 'identity', + * NOT 'eap start', so we should check for that.... + */ + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: + RDEBUG("Got tunneled Access-Accept"); + + for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) { + if (vp->da->attr == PW_EAP_EMSK) { + // FIXME check if we should be generating an emsk from MPPE keys below + emsklen = MIN(vp->vp_length, sizeof(emsk)); + memcpy(emsk, vp->vp_octets, emsklen); + break; + } + + if (vp->da->vendor != VENDORPEC_MICROSOFT) continue; + + /* like for EAP-FAST, the keying material is used reversed */ + switch (vp->da->attr) { + case PW_MSCHAP_MPPE_SEND_KEY: + if (vp->vp_length == EAPTLS_MPPE_KEY_LEN) { + /* do not set emsklen here so not to blat EAP-EMSK */ + // emsklen = sizeof(emsk); + memcpy(emsk, vp->vp_octets, EAPTLS_MPPE_KEY_LEN); + } else if (vp->vp_length == CHAP_VALUE_LENGTH) { + msklen = sizeof(msk); + memcpy(msk, vp->vp_octets, CHAP_VALUE_LENGTH); + } else { + wrong_length: + REDEBUG("Found %s with incorrect length. Expected %u or %u, got %zu", + vp->da->name, CHAP_VALUE_LENGTH, EAPTLS_MPPE_KEY_LEN, vp->vp_length); + return RLM_MODULE_INVALID; + } + + RDEBUGHEX("MSCHAP_MPPE_SEND_KEY [low MSK]", vp->vp_octets, vp->length); + break; + + case PW_MSCHAP_MPPE_RECV_KEY: + /* only do this if there is no EAP-EMSK */ + if (vp->vp_length == EAPTLS_MPPE_KEY_LEN && emsklen == 0) { + msklen = sizeof(msk); + memcpy(msk, vp->vp_octets, EAPTLS_MPPE_KEY_LEN); + emsklen = sizeof(emsk); + memcpy(&emsk[EAPTLS_MPPE_KEY_LEN], vp->vp_octets, EAPTLS_MPPE_KEY_LEN); + } else if (vp->vp_length == CHAP_VALUE_LENGTH) { + msklen = sizeof(msk); + memcpy(&msk[CHAP_VALUE_LENGTH], vp->vp_octets, CHAP_VALUE_LENGTH); + } else { + goto wrong_length; + } + + RDEBUGHEX("MSCHAP_MPPE_RECV_KEY [high MSK]", vp->vp_octets, vp->vp_length); + break; + + case PW_MSCHAP2_SUCCESS: + RDEBUG("Got %s, tunneling it to the client in a challenge", vp->da->name); + if (t->use_tunneled_reply) { + t->authenticated = true; + /* + * Clean up the tunneled reply. + */ + fr_pair_delete_by_num(&reply->vps, PW_PROXY_STATE, 0, TAG_ANY); + fr_pair_delete_by_num(&reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY); + fr_pair_delete_by_num(&reply->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY); + + /* + * Delete MPPE keys & encryption policy. We don't + * want these here. + */ + fr_pair_delete_by_num(&reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_delete_by_num(&reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_delete_by_num(&reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_delete_by_num(&reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); + + fr_pair_list_free(&t->accept_vps); /* for proxying MS-CHAP2 */ + fr_pair_list_mcopy_by_num(t, &t->accept_vps, &reply->vps, 0, 0, TAG_ANY); + rad_assert(!reply->vps); + } + break; + + default: + break; + } + } + + if (t->use_tunneled_reply) { + /* + * Clean up the tunneled reply. + */ + fr_pair_delete_by_num(&reply->vps, PW_EAP_EMSK, 0, TAG_ANY); + fr_pair_delete_by_num(&reply->vps, PW_EAP_SESSION_ID, 0, TAG_ANY); + } + + eap_teap_append_result(tls_session, reply->code); + eap_teap_append_crypto_binding(request, tls_session, msk, msklen, emsk, emsklen); + + vp = fr_pair_find_by_num(request->state, PW_EAP_TEAP_TLV_IDENTITY, VENDORPEC_FREERADIUS, TAG_ANY); + if (vp) { + RDEBUG("&session-state:FreeRADIUS-EAP-TEAP-TLV-Identity-Type set so continuing EAP sequence/chaining"); + + /* RFC3748, Section 2.1 - does not explictly tell us to but we need to eat the EAP-Success */ + fr_pair_delete_by_num(&reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY); + + /* new identity */ + talloc_free(t->username); + t->username = NULL; + + /* RFC7170, Appendix C.6 */ + eap_teap_append_identity(tls_session, vp->vp_short); + eap_teap_append_eap_identity_request(request, tls_session, eap_session); + + goto challenge; + } + + t->result_final = true; + eap_teap_append_result(tls_session, reply->code); + + tls_session->authentication_success = true; + rcode = RLM_MODULE_OK; + + break; + + case PW_CODE_ACCESS_REJECT: + RDEBUG("Got tunneled Access-Reject"); + + eap_teap_append_result(tls_session, reply->code); + rcode = RLM_MODULE_REJECT; + break; + + /* + * Handle Access-Challenge, but only if we + * send tunneled reply data. This is because + * an Access-Challenge means that we MUST tunnel + * a Reply-Message to the client. + */ + case PW_CODE_ACCESS_CHALLENGE: + RDEBUG("Got tunneled Access-Challenge"); +challenge: + /* + * Keep the State attribute, if necessary. + * + * Get rid of the old State, too. + */ + fr_pair_list_free(&t->state); + fr_pair_list_mcopy_by_num(t, &t->state, &reply->vps, PW_STATE, 0, TAG_ANY); + + /* + * Copy the EAP-Message back to the tunnel. + */ + (void) fr_cursor_init(&cursor, &reply->vps); + + while ((vp = fr_cursor_next_by_num(&cursor, PW_EAP_MESSAGE, 0, TAG_ANY)) != NULL) { + eap_teap_tlv_append(tls_session, EAP_TEAP_TLV_EAP_PAYLOAD, true, vp->vp_length, vp->vp_octets); + } + + /* + * When chaining, we 'goto challenge' and can use that to now signal back + * to unlang that a method has completed and we can now move to the next + */ + rcode = reply->code == PW_CODE_ACCESS_CHALLENGE ? RLM_MODULE_HANDLED : RLM_MODULE_OK; + break; + + default: + RDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code); + rcode = RLM_MODULE_INVALID; + break; + } + + + return rcode; +} + +static PW_CODE eap_teap_eap_payload(REQUEST *request, eap_handler_t *eap_session, + tls_session_t *tls_session, + VALUE_PAIR *vp_eap, VALUE_PAIR *vp_type) +{ + PW_CODE code = PW_CODE_ACCESS_REJECT; + rlm_rcode_t rcode; + VALUE_PAIR *vp; + teap_tunnel_t *t; + REQUEST *fake; + + RDEBUG("Processing received EAP Payload"); + + /* + * Allocate a fake REQUEST structure. + */ + fake = request_alloc_fake(request); + rad_assert(!fake->packet->vps); + + t = (teap_tunnel_t *) tls_session->opaque; + + /* + * Add the tunneled attributes to the fake request. + */ + + fake->packet->vps = fr_pair_afrom_num(fake->packet, PW_EAP_MESSAGE, 0); + fr_pair_value_memcpy(fake->packet->vps, vp_eap->vp_octets, vp_eap->vp_length); + + if (vp_type) fr_pair_add(&fake->packet->vps, fr_pair_copy(fake->packet, vp_type)); + + RDEBUG("Got tunneled request"); + rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); + + /* + * Tell the request that it's a fake one. + */ + fr_pair_make(fake->packet, &fake->packet->vps, "Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); + + /* + * No User-Name in the stored data, look for + * an EAP-Identity, and pull it out of there. + */ + if (!t->username) { + vp = fr_pair_find_by_num(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); + if (vp && + (vp->vp_length >= EAP_HEADER_LEN + 2) && + (vp->vp_strvalue[0] == PW_EAP_RESPONSE) && + (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) && + (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { + /* + * Create & remember a User-Name + */ + t->username = fr_pair_make(t, NULL, "User-Name", NULL, T_OP_EQ); + rad_assert(t->username != NULL); + + fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); + + RDEBUG("Got tunneled identity of %s", t->username->vp_strvalue); + } else { + /* + * Don't reject the request outright, + * as it's permitted to do EAP without + * user-name. + */ + RWDEBUG2("No EAP-Identity found to start EAP conversation"); + } + } /* else there WAS a t->username */ + + if (t->username) { + vp = fr_pair_list_copy(fake->packet, t->username); + fr_pair_add(&fake->packet->vps, vp); + fake->username = vp; + } + + /* + * Add the State attribute, too, if it exists. + */ + if (t->state) { + vp = fr_pair_list_copy(fake->packet, t->state); + if (vp) fr_pair_add(&fake->packet->vps, vp); + } + + if (t->stage == AUTHENTICATION) { + VALUE_PAIR *tvp; + + RDEBUG2("AUTHENTICATION"); + + if (t->default_method) { + /* + * RFC 7170 - Authenticating Using EAP-TEAP-MSCHAPv2 + */ + if (t->default_method == PW_EAP_MSCHAPV2 && t->mode == EAP_TEAP_PROVISIONING_ANON) { + tvp = fr_pair_afrom_num(fake, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT); + //fr_pair_value_memcpy(tvp, t->keyblock->server_challenge, CHAP_VALUE_LENGTH); + fr_pair_add(&fake->config, tvp); + + tvp = fr_pair_afrom_num(fake, PW_MS_CHAP_PEER_CHALLENGE, 0); + //fr_pair_value_memcpy(tvp, t->keyblock->client_challenge, CHAP_VALUE_LENGTH); + fr_pair_add(&fake->config, tvp); + } + } + } + + if (t->copy_request_to_tunnel) { + eapteap_copy_request_to_tunnel(request, fake); + } + + if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { + fake->server = vp->vp_strvalue; + + } else if (t->virtual_server) { + fake->server = t->virtual_server; + + } /* else fake->server == request->server */ + + /* + * Call authentication recursively, which will + * do PAP, CHAP, MS-CHAP, etc. + */ + rad_virtual_server(fake); + + /* + * Decide what to do with the reply. + */ + switch (fake->reply->code) { + case 0: + RDEBUG("No tunneled reply was found, rejecting the user."); + code = PW_CODE_ACCESS_REJECT; + break; + + default: + /* + * Returns RLM_MODULE_FOO, and we want to return PW_FOO + */ + rcode = process_reply(eap_session, tls_session, request, fake->reply); + switch (rcode) { + case RLM_MODULE_REJECT: + code = PW_CODE_ACCESS_REJECT; + break; + + case RLM_MODULE_HANDLED: + code = PW_CODE_ACCESS_CHALLENGE; + break; + + case RLM_MODULE_OK: + code = PW_CODE_ACCESS_ACCEPT; + break; + + default: + code = PW_CODE_ACCESS_REJECT; + break; + } + break; + } + + talloc_free(fake); + + return code; +} + +static PW_CODE eap_teap_crypto_binding(REQUEST *request, UNUSED eap_handler_t *eap_session, + tls_session_t *tls_session, eap_tlv_crypto_binding_tlv_t const *binding) +{ + teap_tunnel_t *t = tls_session->opaque; + uint8_t *buf; + unsigned int olen; + struct crypto_binding_buffer *cbb; + uint8_t mac[EVP_MAX_MD_SIZE]; + unsigned int maclen = sizeof(mac); + unsigned int flags; + struct teap_imck_t *imck = NULL; + + olen = tls_session->outer_tlvs_octets ? talloc_array_length(tls_session->outer_tlvs_octets) : 0; + /* FIXME: include client outer TLVs */ + + buf = talloc_zero_array(request, uint8_t, sizeof(struct crypto_binding_buffer) - 1/*outer_tlvs*/ + olen); + rad_assert(buf != NULL); + + cbb = (struct crypto_binding_buffer *)buf; + + if (binding->version != t->received_version || binding->received_version != EAP_TEAP_VERSION) { + RDEBUG2("Crypto-Binding TLV version mis-match (possible downgrade attack!)"); + return PW_CODE_ACCESS_REJECT; + } + if ((binding->subtype & 0xf) != EAP_TEAP_TLV_CRYPTO_BINDING_SUBTYPE_RESPONSE) { + RDEBUG2("Crypto-Binding TLV unexpected non-response"); + return PW_CODE_ACCESS_REJECT; + } + flags = binding->subtype >> 4; + + CRYPTO_BINDING_BUFFER_INIT(cbb); + memcpy(&cbb->binding, binding, sizeof(cbb->binding) - sizeof(cbb->binding.emsk_compound_mac) - sizeof(cbb->binding.msk_compound_mac)); + if (olen) memcpy(cbb->outer_tlvs, tls_session->outer_tlvs_octets, olen); + + RDEBUGHEX("BUFFER for Compound MAC calculation", buf, talloc_array_length(buf)); + + /* + * we carry forward the S-IMCK[j] based on what we verified for session key generation + * + * https://mailarchive.ietf.org/arch/msg/emu/mXzpSGEn86Zx_fa4f1uULYMhMoM/ + * https://github.com/emu-wg/teap-errata/pull/13 + */ + const EVP_MD *md = SSL_CIPHER_get_handshake_digest(SSL_get_current_cipher(tls_session->ssl)); + if (flags != EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_EMSK) { + HMAC(md, &t->imck_msk.cmk, sizeof(t->imck_msk.cmk), buf, talloc_array_length(buf), mac, &maclen); + if (memcmp(binding->msk_compound_mac, mac, sizeof(binding->msk_compound_mac))) { + RDEBUG2("Crypto-Binding TLV (MSK) mis-match"); + return PW_CODE_ACCESS_REJECT; + } + imck = &t->imck_msk; + } + if (flags != EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_MSK && t->imck_emsk_available) { + HMAC(md, &t->imck_emsk.cmk, sizeof(t->imck_emsk.cmk), buf, talloc_array_length(buf), mac, &maclen); + if (memcmp(binding->emsk_compound_mac, mac, sizeof(binding->emsk_compound_mac))) { + RDEBUG2("Crypto-Binding TLV (EMSK) mis-match"); + return PW_CODE_ACCESS_REJECT; + } + imck = &t->imck_emsk; + } + + if (!imck) imck = &imck_zeros; + + /* IMCK[j] 60 octets => S-IMCK[j] first 40 octets, CMK[j] last 20 octets */ + RDEBUGHEX("S-IMCK[j]", imck->simck, sizeof(imck->simck)); + + uint8_t mk_msk_label[31] = "Session Key Generating Function"; + + struct iovec mk_msk_seed[1] = { + { (void *)mk_msk_label, sizeof(mk_msk_label) } + }; + TLS_PRF(tls_session->ssl, + imck->simck, sizeof(imck->simck), + mk_msk_seed, ARRAY_SIZE(mk_msk_seed), + (uint8_t *)&t->msk, sizeof(t->msk)); + RDEBUGHEX("Derived key (MSK)", t->msk, sizeof(t->msk)); + + uint8_t mk_emsk_label[40] = "Extended Session Key Generating Function"; + struct iovec mk_emsk_seed[1] = { + { (void *)mk_emsk_label, sizeof(mk_emsk_label) } + }; + TLS_PRF(tls_session->ssl, + imck->simck, sizeof(imck->simck), + mk_emsk_seed, ARRAY_SIZE(mk_emsk_seed), + (uint8_t *)&t->emsk, sizeof(t->emsk)); + RDEBUGHEX("Derived key (EMSK)", t->emsk, sizeof(t->emsk)); + + return PW_CODE_ACCESS_ACCEPT; +} + + +static PW_CODE eap_teap_process_tlvs(REQUEST *request, eap_handler_t *eap_session, + tls_session_t *tls_session, VALUE_PAIR *teap_vps) +{ + teap_tunnel_t *t = (teap_tunnel_t *) tls_session->opaque; + VALUE_PAIR *vp, *vp_eap = NULL, *vp_type = NULL; + vp_cursor_t cursor; + PW_CODE code = PW_CODE_ACCESS_ACCEPT; + bool gotintermedresult = false, gotresult = false, gotcryptobinding = false; + + for (vp = fr_cursor_init(&cursor, &teap_vps); vp; vp = fr_cursor_next(&cursor)) { + char *value; + DICT_ATTR const *parent_da = NULL; + parent_da = dict_parent(vp->da->attr, vp->da->vendor); + if (parent_da == NULL || vp->da->vendor != VENDORPEC_FREERADIUS || + ((vp->da->attr & 0xff) != PW_FREERADIUS_EAP_TEAP_TLV)) { + value = vp_aprints(request->packet, vp, '"'); + RDEBUG2("ignoring non-EAP-TEAP TLV %s", value); + talloc_free(value); + continue; + } + + switch (parent_da->attr) { + case PW_FREERADIUS_EAP_TEAP_TLV: + switch (vp->da->attr >> 8) { + case EAP_TEAP_TLV_IDENTITY: + vp_type = vp; + break; + case EAP_TEAP_TLV_EAP_PAYLOAD: + vp_eap = vp; + break; + case EAP_TEAP_TLV_RESULT: + gotresult = true; + if (vp->vp_short != EAP_TEAP_TLV_RESULT_SUCCESS) code = PW_CODE_ACCESS_REJECT; + break; + case EAP_TEAP_TLV_INTERMED_RESULT: + gotintermedresult = true; + if (vp->vp_short != EAP_TEAP_TLV_RESULT_SUCCESS) code = PW_CODE_ACCESS_REJECT; + break; + case EAP_TEAP_TLV_CRYPTO_BINDING: + gotcryptobinding = true; + if (vp->vp_length >= sizeof(eap_tlv_crypto_binding_tlv_t)) + code = eap_teap_crypto_binding(request, eap_session, tls_session, + (eap_tlv_crypto_binding_tlv_t const *)vp->vp_octets); + break; + default: + value = vp_aprints_value(request->packet, vp, '"'); + RDEBUG2("ignoring unknown %s", value); + talloc_free(value); + } + break; + case PW_EAP_TEAP_TLV_PAC: + switch ( ( vp->da->attr >> 16 )) { + case PAC_INFO_PAC_ACK: + if (vp->vp_integer == EAP_TEAP_TLV_RESULT_SUCCESS) { + t->pac.expires = UINT32_MAX; + t->pac.expired = false; + } + break; + case PAC_INFO_PAC_TYPE: + if (vp->vp_integer != PAC_TYPE_TUNNEL) { + RDEBUG("only able to serve Tunnel PAC's, ignoring request"); + break; + } + t->pac.send = true; + break; + default: + value = vp_aprints(request->packet, vp, '"'); + RDEBUG2("ignoring unknown EAP-TEAP-PAC-TLV %s", value); + talloc_free(value); + } + break; + default: + value = vp_aprints(request->packet, vp, '"'); + RDEBUG2("ignoring EAP-TEAP TLV %s", value); + talloc_free(value); + } + + if (code == PW_CODE_ACCESS_REJECT) + return PW_CODE_ACCESS_REJECT; + } + + if (t->stage == AUTHENTICATION) { + if (gotcryptobinding && gotintermedresult) t->stage = PROVISIONING; + /* rollback if we have an EAP sequence (chaining) */ + vp = fr_pair_find_by_num(request->state, PW_EAP_TEAP_TLV_IDENTITY, VENDORPEC_FREERADIUS, TAG_ANY); + if (t->stage == PROVISIONING && !gotresult && vp) t->stage = AUTHENTICATION; + } + if (t->stage == PROVISIONING) { + if (gotcryptobinding && gotresult) t->stage = COMPLETE; + } + + if (vp_eap) + code = eap_teap_eap_payload(request, eap_session, tls_session, vp_eap, vp_type); + + return code; +} + + +static void print_tunneled_data(uint8_t const *data, size_t data_len) +{ + size_t i; + + DEBUG2(" TEAP tunnel data total %zu", data_len); + + if ((rad_debug_lvl > 2) && fr_log_fp) { + for (i = 0; i < data_len; i++) { + if ((i & 0x0f) == 0) fprintf(fr_log_fp, " TEAP tunnel data in %02x: ", (int) i); + + fprintf(fr_log_fp, "%02x ", data[i]); + + if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); + } + if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n"); + } +} + + +/* + * Process the inner tunnel data + */ +PW_CODE eap_teap_process(eap_handler_t *eap_session, tls_session_t *tls_session) +{ + PW_CODE code; + VALUE_PAIR *teap_vps, *vp; + uint8_t const *data; + size_t data_len; + teap_tunnel_t *t; + REQUEST *request = eap_session->request; + + /* + * Just look at the buffer directly, without doing + * record_to_buff. + */ + data_len = tls_session->clean_out.used; + tls_session->clean_out.used = 0; + data = tls_session->clean_out.data; + + t = (teap_tunnel_t *) tls_session->opaque; + + if (rad_debug_lvl > 2) print_tunneled_data(data, data_len); + + /* + * See if the tunneled data is well formed. + */ + if (!eap_teap_verify(request, tls_session, data, data_len)) return PW_CODE_ACCESS_REJECT; + + if (t->stage == TLS_SESSION_HANDSHAKE) { + rad_assert(t->mode == EAP_TEAP_UNKNOWN); + + char buf[256]; + if (strstr(SSL_CIPHER_description(SSL_get_current_cipher(tls_session->ssl), + buf, sizeof(buf)), "Au=None")) { + /* FIXME enforce MSCHAPv2 - RFC 7170 */ + RDEBUG2("Using anonymous provisioning"); + t->mode = EAP_TEAP_PROVISIONING_ANON; + t->pac.send = true; + } else { + if (SSL_session_reused(tls_session->ssl)) { + RDEBUG("Session Resumed from PAC"); + t->mode = EAP_TEAP_NORMAL_AUTH; + } else { + RDEBUG2("Using authenticated provisioning"); + t->mode = EAP_TEAP_PROVISIONING_AUTH; + } + + /* + * Send a new pac at ~0.6 times the lifetime. + */ + if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) { + t->pac.send = true; + } + } + + eap_teap_init_keys(request, tls_session); + + /* RFC7170, Appendix C.6 */ + vp = fr_pair_find_by_num(request->state, PW_EAP_TEAP_TLV_IDENTITY, VENDORPEC_FREERADIUS, TAG_ANY); + if (vp) eap_teap_append_identity(tls_session, vp->vp_short); + + eap_teap_append_eap_identity_request(request, tls_session, eap_session); + + t->stage = AUTHENTICATION; + + tls_handshake_send(request, tls_session); + + return PW_CODE_ACCESS_CHALLENGE; + } + + teap_vps = eap_teap_teap2vp(request, tls_session->ssl, data, data_len, NULL, NULL); + + RDEBUG("Got Tunneled TEAP TLVs"); + rdebug_pair_list(L_DBG_LVL_1, request, teap_vps, NULL); + + code = eap_teap_process_tlvs(request, eap_session, tls_session, teap_vps); + + fr_pair_list_free(&teap_vps); + + if (code == PW_CODE_ACCESS_REJECT) return PW_CODE_ACCESS_REJECT; + + switch (t->stage) { + case AUTHENTICATION: + code = PW_CODE_ACCESS_CHALLENGE; + break; + + case PROVISIONING: + if (!t->result_final) { + t->result_final = true; + eap_teap_append_result(tls_session, code); + } + +#if 0 + if (t->pac.send) { + RDEBUG("Peer requires new PAC"); + eap_teap_send_pac_tunnel(request, tls_session); + code = PW_CODE_ACCESS_CHALLENGE; + break; + } +#endif + + /* FALL-THROUGH */ + + case COMPLETE: +#if 0 + /* + * RFC 7170 - Network Access after EAP-TEAP Provisioning + */ + if (t->pac.type && t->pac.expired) { + REDEBUG("Rejecting expired PAC."); + code = PW_CODE_ACCESS_REJECT; + break; + } + + if (t->mode == EAP_TEAP_PROVISIONING_ANON) { + REDEBUG("Rejecting unauthenticated provisioning"); + code = PW_CODE_ACCESS_REJECT; + break; + } +#endif + /* + * TEAP wants to use it's own MSK, so boo to eap_tls_gen_mppe_keys() + */ + eap_add_reply(request, "MS-MPPE-Recv-Key", t->msk, EAPTLS_MPPE_KEY_LEN); + eap_add_reply(request, "MS-MPPE-Send-Key", &t->msk[EAPTLS_MPPE_KEY_LEN], EAPTLS_MPPE_KEY_LEN); + eap_add_reply(request, "EAP-MSK", t->msk, sizeof(t->msk)); + eap_add_reply(request, "EAP-EMSK", t->emsk, sizeof(t->emsk)); + + break; + + default: + RERROR("Internal sanity check failed in EAP-TEAP at %d", t->stage); + code = PW_CODE_ACCESS_REJECT; + } + + tls_handshake_send(request, tls_session); + + return code; +} diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap.h b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap.h new file mode 100644 index 0000000..d6905ec --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap.h @@ -0,0 +1,275 @@ +/* + * eap_teap.h + * + * Version: $Id$ + * + * Copyright (C) 2022 Network RADIUS SARL <legal@networkradius.com> + * + * This software may not be redistributed in any form without the prior + * written consent of Network RADIUS. + * + * 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. + */ +#ifndef _EAP_TEAP_H +#define _EAP_TEAP_H + +RCSIDH(eap_teap_h, "$Id$") + +#include "eap_tls.h" + +#define EAP_TEAP_VERSION 1 + +#define EAP_TEAP_MSK_LEN 64 +#define EAP_TEAP_EMSK_LEN 64 +#define EAP_TEAP_IMSK_LEN 32 +#define EAP_TEAP_SKS_LEN 40 +#define EAP_TEAP_SIMCK_LEN 40 +#define EAP_TEAP_CMK_LEN 20 + +#define EAP_TEAP_TLV_MANDATORY 0x8000 +#define EAP_TEAP_TLV_TYPE 0x3fff + +#define EAP_TEAP_ERR_TUNNEL_COMPROMISED 2001 +#define EAP_TEAP_ERR_UNEXPECTED_TLV 2002 + +/* intermediate result values also match */ +#define EAP_TEAP_TLV_RESULT_SUCCESS 1 +#define EAP_TEAP_TLV_RESULT_FAILURE 2 + +typedef enum eap_teap_stage_t { + TLS_SESSION_HANDSHAKE = 0, + AUTHENTICATION, + PROVISIONING, + COMPLETE +} eap_teap_stage_t; + +typedef enum eap_teap_auth_type { + EAP_TEAP_UNKNOWN = 0, + EAP_TEAP_PROVISIONING_ANON, + EAP_TEAP_PROVISIONING_AUTH, + EAP_TEAP_NORMAL_AUTH +} eap_teap_auth_type_t; + +typedef enum eap_teap_pac_info_attr_type_t { + PAC_INFO_PAC_KEY = 1, // 1 + PAC_INFO_PAC_OPAQUE, // 2 + PAC_INFO_PAC_LIFETIME, // 3 + PAC_INFO_A_ID, // 4 + PAC_INFO_I_ID, // 5 + PAC_INFO_PAC_RESERVED6, // 6 + PAC_INFO_A_ID_INFO, // 7 + PAC_INFO_PAC_ACK, // 8 + PAC_INFO_PAC_INFO, // 9 + PAC_INFO_PAC_TYPE, // 10 + PAC_INFO_MAX +} eap_teap_pac_info_attr_type_t; + +typedef enum eap_teap_pac_type_t { + PAC_TYPE_TUNNEL = 1, // 1 + PAC_TYPE_MACHINE_AUTH, // 2 + PAC_TYPE_USER_AUTHZ, // 3 + PAC_TYPE_MAX +} eap_teap_pac_type_t; + +#define PAC_KEY_LENGTH 32 +#define PAC_A_ID_LENGTH 16 +#define PAC_I_ID_LENGTH 16 +#define PAC_A_ID_INFO_LENGTH 32 + +/* + * 11 - PAC TLV + */ +typedef struct eap_teap_pac_attr_hdr_t { + uint16_t type; + uint16_t length; +} CC_HINT(__packed__) eap_teap_pac_attr_hdr_t; + +/* + * 11.1 - Key + */ +typedef struct eap_teap_pac_attr_key_t { + eap_teap_pac_attr_hdr_t hdr; + uint8_t data[1]; +} CC_HINT(__packed__) eap_teap_pac_attr_key_t; + +/* + * 11.2 - Opaque + */ +typedef struct eap_teap_pac_attr_opaque_t { + eap_teap_pac_attr_hdr_t hdr; + uint8_t data[1]; +} CC_HINT(__packed__) eap_teap_pac_attr_opaque_t; + +/* + * 11.3 and 11.9.3 - lifetime + */ +typedef struct eap_teap_pac_attr_lifetime_t { + eap_teap_pac_attr_hdr_t hdr; + uint32_t data; // secs since epoch +} CC_HINT(__packed__) eap_teap_pac_attr_lifetime_t; + +/* + * 11.4 and 11.9.4 - A-ID + */ +typedef struct eap_teap_pac_attr_a_id_t { + eap_teap_pac_attr_hdr_t hdr; + uint8_t data[1]; +} CC_HINT(__packed__) eap_teap_pac_attr_a_id_t; + +/* + * 11.5 and 11.9.5 - I-ID + */ +typedef struct eap_teap_pac_attr_i_id_t { + eap_teap_pac_attr_hdr_t hdr; + uint8_t data[1]; +} CC_HINT(__packed__) eap_teap_pac_attr_i_id_t; + +/* + * 11.7 and 11.9.7 - A-ID-Info + */ +typedef struct eap_teap_pac_attr_a_id_info_t { + eap_teap_pac_attr_hdr_t hdr; + uint8_t data[1]; +} CC_HINT(__packed__) eap_teap_pac_attr_a_id_info_t; + +/* + * 11.8 - Acknowledgement + */ +typedef struct eap_teap_pac_pac_attr_acknowlegement_t { + eap_teap_pac_attr_hdr_t hdr; + uint16_t data; /* 1 = success, 2 = failure */ +} CC_HINT(__packed__) eap_teap_pac_pac_attr_acknowlegement_t; + +/* + * 11.9 - Info + * + * MUST contain A-ID (4), A-ID-Info (7), and PAC-Type (10). MAY contain others. + */ +typedef struct eap_teap_pac_pac_attr_info_t { + eap_teap_pac_attr_hdr_t hdr; + uint8_t data[1]; /* sub TLVs */ +} CC_HINT(__packed__) eap_teap_pac_pac_attr_info_t; + +/* + * 11.10 and 11.9.10 - PAC Type + */ +typedef struct eap_teap_pac_attr_pac_type_t { + eap_teap_pac_attr_hdr_t hdr; + uint16_t data; /* 1 = Tunnel-PAC */ +} CC_HINT(__packed__) eap_teap_pac_attr_pac_type_t; + +/* RFC 7170, Section 4.2.13 - Crypto-Binding TLV */ +typedef struct eap_tlv_crypto_binding_tlv_t { + uint8_t reserved; + uint8_t version; + uint8_t received_version; + uint8_t subtype; /* Flags[4b] and Sub-Type[4b] */ + uint8_t nonce[32]; + uint8_t emsk_compound_mac[20]; + uint8_t msk_compound_mac[20]; +} CC_HINT(__packed__) eap_tlv_crypto_binding_tlv_t; + +typedef enum eap_teap_tlv_type_t { + EAP_TEAP_TLV_RESERVED_0 = 0, // 0 + EAP_TEAP_TLV_AUTHORITY, // 1 + EAP_TEAP_TLV_IDENTITY, // 2 + EAP_TEAP_TLV_RESULT, // 3 + EAP_TEAP_TLV_NAK, // 4 + EAP_TEAP_TLV_ERROR, // 5 + EAP_TEAP_TLV_CHANNEL_BINDING, // 6 + EAP_TEAP_TLV_VENDOR_SPECIFIC, // 7 + EAP_TEAP_TLV_REQUEST_ACTION, // 8 + EAP_TEAP_TLV_EAP_PAYLOAD, // 9 + EAP_TEAP_TLV_INTERMED_RESULT, // 10 + EAP_TEAP_TLV_PAC, // 11 + EAP_TEAP_TLV_CRYPTO_BINDING, // 12 + EAP_TEAP_TLV_BASIC_PASSWORD_AUTH_REQ, // 13 + EAP_TEAP_TLV_BASIC_PASSWORD_AUTH_RESP, // 14 + EAP_TEAP_TLV_PKCS7, // 15 + EAP_TEAP_TLV_PKCS10, // 16 + EAP_TEAP_TLV_TRUSTED_ROOT, // 17 + EAP_TEAP_TLV_MAX +} eap_teap_tlv_type_t; + +typedef enum eap_teap_tlv_crypto_binding_tlv_flags_t { + EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_EMSK = 1, // 1 + EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_MSK, // 2 + EAP_TEAP_TLV_CRYPTO_BINDING_FLAGS_CMAC_BOTH // 3 +} eap_teap_tlv_crypto_binding_tlv_flags_t; + +typedef enum eap_teap_tlv_crypto_binding_tlv_subtype_t { + EAP_TEAP_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST = 0, // 0 + EAP_TEAP_TLV_CRYPTO_BINDING_SUBTYPE_RESPONSE // 1 +} eap_teap_tlv_crypto_binding_tlv_subtype_t; + +typedef struct teap_imck_t { + uint8_t simck[EAP_TEAP_SIMCK_LEN]; + uint8_t cmk[EAP_TEAP_CMK_LEN]; +} CC_HINT(__packed__) teap_imck_t; +typedef struct teap_tunnel_t { + VALUE_PAIR *username; + VALUE_PAIR *state; + VALUE_PAIR *accept_vps; + bool copy_request_to_tunnel; + bool use_tunneled_reply; + + bool authenticated; + int received_version; + + int mode; + eap_teap_stage_t stage; + + int imckc; + bool imck_emsk_available; + struct teap_imck_t imck_msk; + struct teap_imck_t imck_emsk; + + uint8_t msk[EAP_TEAP_MSK_LEN]; + uint8_t emsk[EAP_TEAP_EMSK_LEN]; + + int default_method; + + uint32_t pac_lifetime; + char const *authority_identity; + uint8_t const *a_id; + uint8_t const *pac_opaque_key; + + struct { + uint8_t *key; + eap_teap_pac_type_t type; + uint32_t expires; + bool expired; + bool send; + } pac; + + bool result_final; + +#ifdef WITH_PROXY + bool proxy_tunneled_request_as_eap; //!< Proxy tunneled session as EAP, or as de-capsulated + //!< protocol. +#endif + char const *virtual_server; +} teap_tunnel_t; + +/* + * Process the TEAP portion of an EAP-TEAP request. + */ +PW_CODE eap_teap_process(eap_handler_t *handler, tls_session_t *tls_session) CC_HINT(nonnull); + +/* + * A bunch of EAP-TEAP helper functions. + */ +VALUE_PAIR *eap_teap_teap2vp(REQUEST *request, UNUSED SSL *ssl, uint8_t const *data, + size_t data_len, DICT_ATTR const *teap_da, vp_cursor_t *out); + +#endif /* _EAP_TEAP_H */ diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap_crypto.c b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap_crypto.c new file mode 100644 index 0000000..17f49f9 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap_crypto.c @@ -0,0 +1,198 @@ +/* + * teap-crypto.c Cryptographic functions for EAP-TEAP. + * + * Version: $Id$ + * + * Copyright (C) 2022 Network RADIUS SARL <legal@networkradius.com> + * + * This software may not be redistributed in any form without the prior + * written consent of Network RADIUS. + * + * 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. + */ + +RCSID("$Id$") +USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + +#include <stdio.h> +#include <freeradius-devel/libradius.h> + +#include <openssl/evp.h> +#include <openssl/aes.h> +#include <openssl/err.h> + +#include "eap_teap_crypto.h" + +# define DEBUG if (fr_debug_lvl && fr_log_fp) fr_printf_log + +static void debug_errors(void) +{ + unsigned long errCode; + + while((errCode = ERR_get_error())) { + char *err = ERR_error_string(errCode, NULL); + DEBUG("EAP-TEAP error in OpenSSL - %s", err); + } +} + +// https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode +int eap_teap_encrypt(uint8_t const *plaintext, size_t plaintext_len, + uint8_t const *aad, size_t aad_len, + uint8_t const *key, uint8_t *iv, unsigned char *ciphertext, + uint8_t *tag) +{ + EVP_CIPHER_CTX *ctx; + + int len; + + int ciphertext_len; + + + /* Create and initialise the context */ + if (!(ctx = EVP_CIPHER_CTX_new())) { + debug_errors(); + return -1; + }; + + /* Initialise the encryption operation. */ + if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) { + debug_errors(); + return -1; + }; + + /* Set IV length if default 12 bytes (96 bits) is not appropriate */ + if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL)) { + debug_errors(); + return -1; + }; + + /* Initialise key and IV */ + if (1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) { + debug_errors(); + return -1; + }; + + /* Provide any AAD data. This can be called zero or more times as + * required + */ + if (1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)) { + debug_errors(); + return -1; + }; + + /* Provide the message to be encrypted, and obtain the encrypted output. + * EVP_EncryptUpdate can be called multiple times if necessary + */ + if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) { + debug_errors(); + return -1; + }; + ciphertext_len = len; + + /* Finalise the encryption. Normally ciphertext bytes may be written at + * this stage, but this does not occur in GCM mode + */ + if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { + debug_errors(); + return -1; + }; + ciphertext_len += len; + + /* Get the tag */ + if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) { + debug_errors(); + return -1; + }; + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + return ciphertext_len; +} + +int eap_teap_decrypt(uint8_t const *ciphertext, size_t ciphertext_len, + uint8_t const *aad, size_t aad_len, + uint8_t const *tag, uint8_t const *key, uint8_t const *iv, uint8_t *plaintext) +{ + EVP_CIPHER_CTX *ctx; + int len; + int plaintext_len; + int ret; + + /* Create and initialise the context */ + if (!(ctx = EVP_CIPHER_CTX_new())) { + debug_errors(); + return -1; + }; + + /* Initialise the decryption operation. */ + if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) { + debug_errors(); + return -1; + }; + + /* Set IV length. Not necessary if this is 12 bytes (96 bits) */ + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL)) { + debug_errors(); + return -1; + }; + + /* Initialise key and IV */ + if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { + debug_errors(); + return -1; + }; + + /* Provide any AAD data. This can be called zero or more times as + * required + */ + if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len)) { + debug_errors(); + return -1; + }; + + /* Provide the message to be decrypted, and obtain the plaintext output. + * EVP_DecryptUpdate can be called multiple times if necessary + */ + if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { + debug_errors(); + return -1; + }; + plaintext_len = len; + + { + unsigned char *tmp; + + memcpy(&tmp, &tag, sizeof(tmp)); + + /* Set expected tag value. Works in OpenSSL 1.0.1d and later */ + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tmp)) { + debug_errors(); + return -1; + }; + } + + /* Finalise the decryption. A positive return value indicates success, + * anything else is a failure - the plaintext is not trustworthy. + */ + ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len); + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + if (ret < 0) return -1; + + /* Success */ + plaintext_len += len; + return plaintext_len; +} diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap_crypto.h b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap_crypto.h new file mode 100644 index 0000000..b02f2b9 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/eap_teap_crypto.h @@ -0,0 +1,39 @@ +/* + * eap_teap_crypto.h + * + * Version: $Id$ + * + * Copyright (C) 2022 Network RADIUS SARL <legal@networkradius.com> + * + * This software may not be redistributed in any form without the prior + * written consent of Network RADIUS. + * + * 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. + */ + +#ifndef _EAP_TEAP_CRYPTO_H +#define _EAP_TEAP_CRYPTO_H + +RCSIDH(eap_teap_crypto_h, "$Id$") + + +int eap_teap_encrypt(uint8_t const *plaintext, size_t plaintext_len, + uint8_t const *aad, size_t aad_len, + uint8_t const *key, uint8_t *iv, unsigned char *ciphertext, + uint8_t *tag); + +int eap_teap_decrypt(uint8_t const *ciphertext, size_t ciphertext_len, + uint8_t const *aad, size_t aad_len, + uint8_t const *tag, uint8_t const *key, uint8_t const *iv, uint8_t *plaintext); + +#endif /* _EAP_TEAP_CRYPTO_H */ diff --git a/src/modules/rlm_eap/types/rlm_eap_teap/rlm_eap_teap.c b/src/modules/rlm_eap/types/rlm_eap_teap/rlm_eap_teap.c new file mode 100644 index 0000000..5f2a8db --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_teap/rlm_eap_teap.c @@ -0,0 +1,388 @@ +/* + * rlm_eap_teap.c contains the interfaces that are called from eap + * + * Version: $Id$ + * + * Copyright (C) 2022 Network RADIUS SARL <legal@networkradius.com> + * + * This software may not be redistributed in any form without the prior + * written consent of Network RADIUS. + * + * 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. + */ + +RCSID("$Id$") +USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + +#include "eap_teap.h" + +typedef struct rlm_eap_teap_t { + /* + * TLS configuration + */ + char const *tls_conf_name; + fr_tls_server_conf_t *tls_conf; + + /* + * Default tunneled EAP type + */ + char const *default_method_name; + int default_method; + + /* + * Use the reply attributes from the tunneled session in + * the non-tunneled reply to the client. + */ + bool use_tunneled_reply; + + /* + * Use SOME of the request attributes from outside of the + * tunneled session in the tunneled request + */ + bool copy_request_to_tunnel; + + /* + * Do we do require a client cert? + */ + bool req_client_cert; + + uint32_t pac_lifetime; + char const *authority_identity; + char const *pac_opaque_key; + + /* + * Virtual server for inner tunnel session. + */ + char const *virtual_server; +} rlm_eap_teap_t; + + +static CONF_PARSER module_config[] = { + { "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_teap_t, tls_conf_name), NULL }, + { "default_eap_type", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_teap_t, default_method_name), "md5" }, + { "copy_request_to_tunnel", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_teap_t, copy_request_to_tunnel), "no" }, + { "use_tunneled_reply", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_teap_t, use_tunneled_reply), "no" }, + { "require_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_teap_t, req_client_cert), "no" }, + { "pac_lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_eap_teap_t, pac_lifetime), "604800" }, + { "authority_identity", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_eap_teap_t, authority_identity), NULL }, + { "pac_opaque_key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_eap_teap_t, pac_opaque_key), NULL }, + { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_teap_t, virtual_server), NULL }, + CONF_PARSER_TERMINATOR +}; + + +/* + * Attach the module. + */ +static int mod_instantiate(CONF_SECTION *cs, void **instance) +{ + rlm_eap_teap_t *inst; + + *instance = inst = talloc_zero(cs, rlm_eap_teap_t); + if (!inst) return -1; + + /* + * Parse the configuration attributes. + */ + if (cf_section_parse(cs, inst, module_config) < 0) { + return -1; + } + + if (!inst->virtual_server) { + ERROR("rlm_eap_teap: A 'virtual_server' MUST be defined for security"); + return -1; + } + + /* + * Convert the name to an integer, to make it easier to + * handle. + */ + if (inst->default_method_name && *inst->default_method_name) { + inst->default_method = eap_name2type(inst->default_method_name); + if (inst->default_method < 0) { + ERROR("rlm_eap_teap: Unknown EAP type %s", + inst->default_method_name); + return -1; + } + } + + /* + * Read tls configuration, either from group given by 'tls' + * option, or from the eap-tls configuration. + */ + inst->tls_conf = eaptls_conf_parse(cs, "tls"); + + if (!inst->tls_conf) { + ERROR("rlm_eap_teap: Failed initializing SSL context"); + return -1; + } + + return 0; +} + +/* + * Allocate the TEAP per-session data + */ +static teap_tunnel_t *teap_alloc(TALLOC_CTX *ctx, rlm_eap_teap_t *inst) +{ + teap_tunnel_t *t; + + t = talloc_zero(ctx, teap_tunnel_t); + + t->received_version = -1; + t->default_method = inst->default_method; + t->copy_request_to_tunnel = inst->copy_request_to_tunnel; + t->use_tunneled_reply = inst->use_tunneled_reply; + t->virtual_server = inst->virtual_server; + return t; +} + + +/* + * Send an initial eap-tls request to the peer, using the libeap functions. + */ +static int mod_session_init(void *type_arg, eap_handler_t *handler) +{ + int status; + tls_session_t *ssn; + rlm_eap_teap_t *inst; + VALUE_PAIR *vp; + bool client_cert; + REQUEST *request = handler->request; + + inst = type_arg; + + handler->tls = true; + + /* + * Check if we need a client certificate. + */ + + /* + * EAP-TLS-Require-Client-Cert attribute will override + * the require_client_cert configuration option. + */ + vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY); + if (vp) { + client_cert = vp->vp_integer ? true : false; + } else { + client_cert = inst->req_client_cert; + } + + /* + * Disallow TLS 1.3 for now. + */ + ssn = eaptls_session(handler, inst->tls_conf, client_cert, false); + if (!ssn) { + return 0; + } + + handler->opaque = ((void *)ssn); + + /* + * As TEAP is a unique special snowflake and wants to use its + * own rolling MSK for MPPE we we set the label to NULL so in that + * eaptls_gen_mppe_keys() is NOT called in eaptls_success. + */ + ssn->label = NULL; + + /* + * Really just protocol version. + */ + ssn->peap_flag = EAP_TEAP_VERSION; + + /* + * hostapd's wpa_supplicant gets upset if we include all the + * S+L+O flags but is happy with S+O (TLS payload is zero bytes + * for S anyway) - FIXME not true for early-data TLSv1.3! + */ + ssn->length_flag = false; + + vp = fr_pair_make(ssn, NULL, "FreeRADIUS-EAP-TEAP-Authority-ID", inst->authority_identity, T_OP_EQ); + fr_pair_add(&ssn->outer_tlvs, vp); + + /* + * TLS session initialization is over. Now handle TLS + * related handshaking or application data. + */ + status = eaptls_request(handler->eap_ds, ssn, true); + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { + RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + if (status == 0) return 0; + + /* + * The next stage to process the packet. + */ + handler->stage = PROCESS; + + return 1; +} + + +/* + * Do authentication, by letting EAP-TLS do most of the work. + */ +static int mod_process(void *arg, eap_handler_t *handler) +{ + int rcode; + int ret = 0; + fr_tls_status_t status; + rlm_eap_teap_t *inst = (rlm_eap_teap_t *) arg; + tls_session_t *tls_session = (tls_session_t *) handler->opaque; + teap_tunnel_t *t = (teap_tunnel_t *) tls_session->opaque; + REQUEST *request = handler->request; + + RDEBUG2("Authenticate"); + + /* + * Process TLS layer until done. + */ + status = eaptls_process(handler); + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } else { + RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); + } + + /* + * Make request available to any SSL callbacks + */ + SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request); + switch (status) { + /* + * EAP-TLS handshake was successful, tell the + * client to keep talking. + * + * If this was EAP-TLS, we would just return + * an EAP-TLS-Success packet here. + */ + case FR_TLS_SUCCESS: + if (SSL_session_reused(tls_session->ssl)) { + RDEBUG("Skipping Phase2 due to session resumption"); + goto do_keys; + } + + if (t && t->authenticated) { + if (t->accept_vps) { + RDEBUG2("Using saved attributes from the original Access-Accept"); + rdebug_pair_list(L_DBG_LVL_2, request, t->accept_vps, NULL); + fr_pair_list_mcopy_by_num(handler->request->reply, + &handler->request->reply->vps, + &t->accept_vps, 0, 0, TAG_ANY); + } else if (t->use_tunneled_reply) { + RDEBUG2("No saved attributes in the original Access-Accept"); + } + + do_keys: + /* + * Success: Automatically return MPPE keys. + */ + ret = eaptls_success(handler, 0); + goto done; + } + goto phase2; + + /* + * The TLS code is still working on the TLS + * exchange, and it's a valid TLS request. + * do nothing. + */ + case FR_TLS_HANDLED: + ret = 1; + goto done; + + /* + * Handshake is done, proceed with decoding tunneled + * data. + */ + case FR_TLS_OK: + break; + + /* + * Anything else: fail. + */ + default: + ret = 0; + goto done; + } + +phase2: + /* + * Session is established, proceed with decoding + * tunneled data. + */ + RDEBUG2("Session established. Proceeding to decode tunneled attributes"); + + /* + * We may need TEAP data associated with the session, so + * allocate it here, if it wasn't already alloacted. + */ + if (!tls_session->opaque) { + tls_session->opaque = teap_alloc(tls_session, inst); + t = (teap_tunnel_t *) tls_session->opaque; + if (t->received_version < 0) t->received_version = handler->eap_ds->response->type.data[0] & 0x07; + } + + /* + * Process the TEAP portion of the request. + */ + rcode = eap_teap_process(handler, tls_session); + switch (rcode) { + case PW_CODE_ACCESS_REJECT: + eaptls_fail(handler, 0); + ret = 0; + goto done; + + /* + * Access-Challenge, continue tunneled conversation. + */ + case PW_CODE_ACCESS_CHALLENGE: + eaptls_request(handler->eap_ds, tls_session, false); + ret = 1; + goto done; + + /* + * Success: Automatically return MPPE keys. + */ + case PW_CODE_ACCESS_ACCEPT: + goto do_keys; + + default: + break; + } + + /* + * Something we don't understand: Reject it. + */ + eaptls_fail(handler, 0); + +done: + SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, NULL); + + return ret; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + */ +extern rlm_eap_module_t rlm_eap_teap; +rlm_eap_module_t rlm_eap_teap = { + .name = "eap_teap", + .instantiate = mod_instantiate, /* Create new submodule instance */ + .session_init = mod_session_init, /* Initialise a new EAP session */ + .process = mod_process /* Process next round of EAP method */ +}; diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c index d327c57..482fdca 100644 --- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c @@ -72,19 +72,6 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) return -1; } -#ifdef TLS1_3_VERSION - if ((inst->tls_conf->max_version == TLS1_3_VERSION) || - (inst->tls_conf->min_version == TLS1_3_VERSION)) { - WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3"); - WARN("!! Please set tls_max_version = \"1.2\""); - WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); - WARN("!! This limitation is likely to change in late 2021."); - WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); - WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - } -#endif - return 0; } @@ -135,7 +122,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) * TLS session initialization is over. Now handle TLS * related handshaking or application data. */ - status = eaptls_start(handler->eap_ds, ssn->peap_flag); + status = eaptls_request(handler->eap_ds, ssn, true); if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); } else { diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c index 4e53c92..6fd3abf 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c @@ -155,12 +155,10 @@ static ttls_tunnel_t *ttls_alloc(TALLOC_CTX *ctx, rlm_eap_ttls_t *inst) */ static int mod_session_init(void *type_arg, eap_handler_t *handler) { - int status; tls_session_t *ssn; rlm_eap_ttls_t *inst; VALUE_PAIR *vp; bool client_cert; - REQUEST *request = handler->request; inst = type_arg; @@ -204,13 +202,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) * TLS session initialization is over. Now handle TLS * related handshaking or application data. */ - status = eaptls_start(handler->eap_ds, ssn->peap_flag); - if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); - } else { - RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); - } - if (status == 0) return 0; + eaptls_start(handler->eap_ds, ssn->peap_flag); /* * The next stage to process the packet. @@ -284,7 +276,7 @@ static int mod_process(void *arg, eap_handler_t *handler) ret = eaptls_success(handler, 0); goto done; } else { - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); } ret = 1; goto done; @@ -341,7 +333,7 @@ static int mod_process(void *arg, eap_handler_t *handler) * Access-Challenge, continue tunneled conversation. */ case PW_CODE_ACCESS_CHALLENGE: - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); ret = 1; goto done; diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c index cbe4239..d997e3e 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c @@ -877,6 +877,8 @@ static int CC_HINT(nonnull) eapttls_postproxy(eap_handler_t *handler, void *data request->proxy_reply = talloc_steal(request, fake->reply); fake->reply = NULL; + request->proxy->dst_port = 0; /* hacks for state.c lookups */ + /* * And we're done with this request. */ @@ -914,7 +916,7 @@ static int CC_HINT(nonnull) eapttls_postproxy(eap_handler_t *handler, void *data case RLM_MODULE_HANDLED: RDEBUG("Reply was handled"); - eaptls_request(handler->eap_ds, tls_session); + eaptls_request(handler->eap_ds, tls_session, false); request->proxy_reply->code = PW_CODE_ACCESS_CHALLENGE; return 1; diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c index 1aad02d..c5ebd34 100644 --- a/src/modules/rlm_expr/rlm_expr.c +++ b/src/modules/rlm_expr/rlm_expr.c @@ -681,7 +681,7 @@ static ssize_t randstr_xlat(UNUSED void *instance, UNUSED REQUEST *request, return -1; } - if (number > 0) { + if (number > 1) { number--; goto redo; } diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c index 08679e6..2d4e871 100644 --- a/src/modules/rlm_files/rlm_files.c +++ b/src/modules/rlm_files/rlm_files.c @@ -347,7 +347,6 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * { char const *name; VALUE_PAIR *check_tmp = NULL; - VALUE_PAIR *reply_tmp = NULL; PAIR_LIST const *user_pl, *default_pl; bool found = false; PAIR_LIST my_pl; @@ -411,6 +410,8 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * default_pl = default_pl->next; } + RDEBUG3("%s: Checking entry %s at line %d", filename, pl->name, pl->lineno); + if (pl->check) { check_tmp = fr_pair_list_copy(request, pl->check); for (vp = fr_cursor_init(&cursor, &check_tmp); @@ -425,32 +426,35 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * } if (paircompare(request, request_packet->vps, check_tmp, &reply_packet->vps) == 0) { + VALUE_PAIR *reply_tmp = NULL; + RDEBUG2("%s: Matched entry %s at line %d", filename, pl->name, pl->lineno); found = true; /* ctx may be reply or proxy */ reply_tmp = fr_pair_list_copy(reply_packet, pl->reply); + + if (reply_tmp) fr_pair_delete_by_num(&reply_tmp, PW_FALL_THROUGH, 0, TAG_ANY); if (reply_tmp) radius_pairmove(request, &reply_packet->vps, reply_tmp, true); fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD); - fr_pair_list_free(&check_tmp); /* - * Fallthrough? + * Check Fall-Through against the original reply list, which has Fall-Through */ if (!fall_through(pl->reply)) break; } - } - /* - * Remove server internal parameters. - */ - fr_pair_delete_by_num(&reply_packet->vps, PW_FALL_THROUGH, 0, TAG_ANY); + /* + * Always free check_tmp, even if the paircompare() didn't match. + */ + fr_pair_list_free(&check_tmp); + } /* * See if we succeeded. */ - if (!found) return RLM_MODULE_NOOP; /* on to the next module */ + if (!found) return RLM_MODULE_NOOP; /* on to the next module */ return RLM_MODULE_OK; diff --git a/src/modules/rlm_json/rlm_json.c b/src/modules/rlm_json/rlm_json.c index 9cf99d8..a2476e8 100644 --- a/src/modules/rlm_json/rlm_json.c +++ b/src/modules/rlm_json/rlm_json.c @@ -81,7 +81,7 @@ static CONF_PARSER const module_config[] = { * @param outlen space available for the output * @return length of output generated */ -static ssize_t json_encode_xlat(UNUSED void * instance, REQUEST *request, char const *fmt, +static ssize_t json_encode_xlat(void * instance, REQUEST *request, char const *fmt, char *out, size_t outlen) { rlm_json_t const *inst = instance; diff --git a/src/modules/rlm_ldap/ldap.c b/src/modules/rlm_ldap/ldap.c index c356921..d35cb20 100644 --- a/src/modules/rlm_ldap/ldap.c +++ b/src/modules/rlm_ldap/ldap.c @@ -1295,8 +1295,9 @@ void rlm_ldap_check_reply(rlm_ldap_t const *inst, REQUEST *request) !fr_pair_find_by_num(request->config, PW_CRYPT_PASSWORD, 0, TAG_ANY)) { RWDEBUG("No \"known good\" password added. Ensure the admin user has permission to " "read the password attribute"); - RWDEBUG("PAP authentication will *NOT* work with Active Directory (if that is what you " + RWDEBUG("CHAP / MS-CHAP authentication will *NOT* work with Active Directory (if that is what you " "were trying to configure)"); + RWDEBUG("PAP authentication to Active Directory *MUST* set 'Auth-Type := LDAP'"); } } } diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c index 00ab90d..4742f9f 100644 --- a/src/modules/rlm_mschap/rlm_mschap.c +++ b/src/modules/rlm_mschap/rlm_mschap.c @@ -1013,16 +1013,16 @@ ntlm_auth_err: return -1; } - if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { - REDEBUG("Failed setting key length"); - goto error; - } - if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) { REDEBUG("Failed setting key value"); goto error;; } + if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { + REDEBUG("Failed setting key length"); + goto error; + } + if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) { REDEBUG("Failed getting output"); goto error; diff --git a/src/modules/rlm_python/rlm_python.c b/src/modules/rlm_python/rlm_python.c index 2adba0e..412859c 100644 --- a/src/modules/rlm_python/rlm_python.c +++ b/src/modules/rlm_python/rlm_python.c @@ -996,6 +996,7 @@ static void *dlopen_libpython(int flags) static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) { int i; + bool locked = false; /* * Explicitly load libpython, so symbols will be available to lib-dynload modules @@ -1023,6 +1024,7 @@ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) Py_InitializeEx(0); /* Don't override signal handlers - noop on subs calls */ PyEval_InitThreads(); /* This also grabs a lock (which we then need to release) */ main_interpreter = PyThreadState_Get(); /* Store reference to the main interpreter */ + locked = true; } rad_assert(PyEval_ThreadsInitialized()); @@ -1041,6 +1043,7 @@ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) inst->sub_interpreter = main_interpreter; } + if (!locked) PyEval_AcquireThread(inst->sub_interpreter); PyThreadState_Swap(inst->sub_interpreter); /* diff --git a/src/modules/rlm_python3/rlm_python3.c b/src/modules/rlm_python3/rlm_python3.c index aaa43ab..48deaa3 100644 --- a/src/modules/rlm_python3/rlm_python3.c +++ b/src/modules/rlm_python3/rlm_python3.c @@ -56,11 +56,11 @@ RCSID("$Id$") static uint32_t python_instances = 0; static void *python_dlhandle; -static PyThreadState *main_interpreter; //!< Main interpreter (cext safe) -static PyObject *main_module; //!< Pthon configuration dictionary. +static PyThreadState *main_interpreter = NULL; //!< Main interpreter (cext safe) +static PyObject *main_module = NULL; //!< Pthon configuration dictionary. -static rlm_python_t *current_inst; //!< Needed to pass parameter to PyInit_radiusd -static CONF_SECTION *current_conf; //!< Needed to pass parameter to PyInit_radiusd +static rlm_python_t *current_inst = NULL; //!< Needed to pass parameter to PyInit_radiusd +static CONF_SECTION *current_conf = NULL; //!< Needed to pass parameter to PyInit_radiusd /* * A mapping of configuration file names to internal variables. @@ -387,6 +387,7 @@ static int mod_populate_vptuple(PyObject *pPair, VALUE_PAIR *vp) ERROR("%s:%d, vp->da->name: %s", __func__, __LINE__, vp->da->name); if (PyErr_Occurred()) { python_error_log(); + PyErr_Clear(); } return -1; @@ -567,6 +568,7 @@ static rlm_rcode_t do_python_single(REQUEST *request, PyObject *pFunc, char cons ERROR("%s:%d, %s - pRet is NULL", __func__, __LINE__, funcname); if (PyErr_Occurred()) { python_error_log(); + PyErr_Clear(); } ret = RLM_MODULE_FAIL; goto finish; @@ -674,17 +676,22 @@ finish: if (ret == RLM_MODULE_FAIL) { ERROR("%s:%d, %s - RLM_MODULE_FAIL", __func__, __LINE__, funcname); } + + if (PyErr_Occurred()) { + ERROR("Unhandled Python exception (see below); clearing."); + python_error_log(); + PyErr_Clear(); + } + return ret; } static void python_interpreter_free(PyThreadState *interp) { -DIAG_OFF(deprecated-declarations) - PyEval_AcquireLock(); + PyEval_RestoreThread(interp); PyThreadState_Swap(interp); Py_EndInterpreter(interp); - PyEval_ReleaseLock(); -DIAG_ON(deprecated-declarations) + PyEval_SaveThread(); } /** Destroy a thread state @@ -1094,6 +1101,8 @@ static PyMODINIT_FUNC PyInit_radiusd(void) */ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) { + bool locked = false; + /* * prepare radiusd module to be loaded */ @@ -1115,7 +1124,33 @@ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) python_dlhandle = dlopen_libpython(RTLD_NOW | RTLD_GLOBAL); if (!python_dlhandle) WARN("Failed loading libpython symbols into global symbol table"); -#if PY_VERSION_HEX >= 0x03050000 +#if PY_VERSION_HEX > 0x030a0000 + { + PyStatus status; + PyConfig config; + wchar_t *name; + + /* + * Isolated config: Don't override signal handlers - noop on subs calls + */ + PyConfig_InitIsolatedConfig(&config); + + MEM(name = Py_DecodeLocale(main_config.name, NULL)); + status = PyConfig_SetString(&config, &config.program_name, name); + PyMem_RawFree(name); + if (PyStatus_Exception(status)) { + PyConfig_Clear(&config); + return -1; + } + + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + PyConfig_Clear(&config); + return -1; + } + PyConfig_Clear(&config); + } +#elif PY_VERSION_HEX >= 0x03050000 { wchar_t *name; @@ -1132,9 +1167,12 @@ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) } #endif +#if PY_VERSION_HEX <= 0x030a0000 Py_InitializeEx(0); /* Don't override signal handlers - noop on subs calls */ PyEval_InitThreads(); /* This also grabs a lock (which we then need to release) */ +#endif main_interpreter = PyThreadState_Get(); /* Store reference to the main interpreter */ + locked = true; } rad_assert(PyEval_ThreadsInitialized()); @@ -1153,6 +1191,7 @@ static int python_interpreter_init(rlm_python_t *inst, CONF_SECTION *conf) inst->sub_interpreter = main_interpreter; } + if (!locked) PyEval_AcquireThread(inst->sub_interpreter); PyThreadState_Swap(inst->sub_interpreter); /* diff --git a/src/modules/rlm_radutmp/rlm_radutmp.c b/src/modules/rlm_radutmp/rlm_radutmp.c index b3d0037..999e6c4 100644 --- a/src/modules/rlm_radutmp/rlm_radutmp.c +++ b/src/modules/rlm_radutmp/rlm_radutmp.c @@ -489,7 +489,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *requ * easier than searching through the entire file. */ if (!cache) { - cache = talloc_zero(NULL, NAS_PORT); + cache = talloc_zero(inst, NAS_PORT); if (cache) { cache->nasaddr = ut.nas_address; cache->port = ut.nas_port; diff --git a/src/modules/rlm_sql/drivers/rlm_sql_freetds/rlm_sql_freetds.c b/src/modules/rlm_sql/drivers/rlm_sql_freetds/rlm_sql_freetds.c index a5c3b93..da7d7fc 100644 --- a/src/modules/rlm_sql/drivers/rlm_sql_freetds/rlm_sql_freetds.c +++ b/src/modules/rlm_sql/drivers/rlm_sql_freetds/rlm_sql_freetds.c @@ -458,7 +458,7 @@ static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t * for (i = 0; i < colcount; i++) { /* Space to hold the result data */ - rowdata[i] = talloc_array(rowdata, char, MAX_DATASTR_LEN + 1); + rowdata[i] = talloc_zero_array(rowdata, char, MAX_DATASTR_LEN + 1); /* Associate the target buffer with the data */ if (ct_bind(conn->command, i + 1, &descriptor, rowdata[i], NULL, NULL) != CS_SUCCEED) { diff --git a/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c b/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c index 78d1b8f..2f51e0a 100644 --- a/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c +++ b/src/modules/rlm_sql/drivers/rlm_sql_mysql/rlm_sql_mysql.c @@ -305,14 +305,11 @@ static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *c * We need to know about connection errors, and are capable * of reconnecting automatically. */ -#if MYSQL_VERSION_ID >= 50013 { int reconnect = 0; mysql_options(&(conn->db), MYSQL_OPT_RECONNECT, &reconnect); } -#endif -#if (MYSQL_VERSION_ID >= 50000) if (config->query_timeout) { unsigned int connect_timeout = config->query_timeout; unsigned int read_timeout = config->query_timeout; @@ -340,13 +337,8 @@ static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *c mysql_options(&(conn->db), MYSQL_OPT_READ_TIMEOUT, &read_timeout); mysql_options(&(conn->db), MYSQL_OPT_WRITE_TIMEOUT, &write_timeout); } -#endif -#if (MYSQL_VERSION_ID >= 40100) sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS; -#else - sql_flags = CLIENT_FOUND_ROWS; -#endif #ifdef CLIENT_MULTI_STATEMENTS sql_flags |= CLIENT_MULTI_STATEMENTS; @@ -485,14 +477,12 @@ retry_store_result: if (!conn->result) { rcode = sql_check_error(conn->sock, 0); if (rcode != RLM_SQL_OK) return rcode; -#if (MYSQL_VERSION_ID >= 40100) ret = mysql_next_result(conn->sock); if (ret == 0) { /* there are more results */ goto retry_store_result; } else if (ret > 0) return sql_check_error(NULL, ret); /* ret == -1 signals no more results */ -#endif } return RLM_SQL_OK; } @@ -502,17 +492,10 @@ static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *con int num = 0; rlm_sql_mysql_conn_t *conn = handle->conn; -#if MYSQL_VERSION_ID >= 32224 /* * Count takes a connection handle */ if (!(num = mysql_field_count(conn->sock))) { -#else - /* - * Fields takes a result struct - */ - if (!(num = mysql_num_fields(conn->result))) { -#endif return -1; } return num; @@ -576,7 +559,6 @@ retry_fetch_row: rcode = sql_check_error(conn->sock, 0); if (rcode != RLM_SQL_OK) return rcode; -#if (MYSQL_VERSION_ID >= 40100) sql_free_result(handle, config); ret = mysql_next_result(conn->sock); @@ -587,7 +569,7 @@ retry_fetch_row: } } else if (ret > 0) return sql_check_error(NULL, ret); /* If ret is -1 then there are no more rows */ -#endif + return RLM_SQL_NO_MORE_ROWS; } @@ -769,7 +751,6 @@ static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen, */ static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config) { -#if (MYSQL_VERSION_ID >= 40100) rlm_sql_mysql_conn_t *conn = handle->conn; int ret; MYSQL_RES *result; @@ -809,7 +790,6 @@ static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t * mysql_free_result(result); } if (ret > 0) return sql_check_error(NULL, ret); -#endif return RLM_SQL_OK; } diff --git a/src/modules/rlm_sql/drivers/rlm_sql_null/rlm_sql_null.c b/src/modules/rlm_sql/drivers/rlm_sql_null/rlm_sql_null.c index 8f3a8f0..a4c961c 100644 --- a/src/modules/rlm_sql/drivers/rlm_sql_null/rlm_sql_null.c +++ b/src/modules/rlm_sql/drivers/rlm_sql_null/rlm_sql_null.c @@ -53,9 +53,9 @@ static sql_rcode_t sql_select_query(UNUSED rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, UNUSED char const *query) { if (rad_debug_lvl >= L_DBG_LVL_1) { - radlog(L_DBG | L_WARN, "The 'rlm_sql_null' driver CANNOT be used for SELECTS."); - radlog(L_DBG | L_WARN, "Please update the 'sql' module configuration to use a real database."); - radlog(L_DBG | L_WARN, "Set 'driver = ...' to the database you want to use."); + radlog(L_DBG, "The 'rlm_sql_null' driver CANNOT be used for SELECTS."); + radlog(L_DBG, "Please update the 'sql' module configuration to use a real database."); + radlog(L_DBG, "Set 'driver = ...' to the database you want to use."); } return 0; diff --git a/src/modules/rlm_sql/drivers/rlm_sql_sqlite/rlm_sql_sqlite.c b/src/modules/rlm_sql/drivers/rlm_sql_sqlite/rlm_sql_sqlite.c index 65b7d9a..3b2a01b 100644 --- a/src/modules/rlm_sql/drivers/rlm_sql_sqlite/rlm_sql_sqlite.c +++ b/src/modules/rlm_sql/drivers/rlm_sql_sqlite/rlm_sql_sqlite.c @@ -232,9 +232,9 @@ static void sql_print_error(sqlite3 *db, int status, char const *fmt, ...) static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, char const *filename) { ssize_t len; - int statement_cnt = 0; + int statement_len, statement_cnt = 0; char *buffer; - char *p, *q; + char const *p; int cl; FILE *f; struct stat finfo; @@ -308,7 +308,7 @@ static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, char const *filename) if ((*p != 0x0a) && (*p != 0x0d) && (*p != '\t')) break; cl = 1; } else { - cl = fr_utf8_char((uint8_t *) p, -1); + cl = fr_utf8_char((uint8_t const *) p, -1); if (!cl) break; } } @@ -319,21 +319,13 @@ static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, char const *filename) return -1; } - /* - * Statement delimiter is ;\n - */ p = buffer; - while ((q = strchr(p, ';'))) { - if ((q[1] != '\n') && (q[1] != '\0')) { - p = q + 1; - statement_cnt++; - continue; - } - + while (*p) { + statement_len = len - (p - buffer); #ifdef HAVE_SQLITE3_PREPARE_V2 - status = sqlite3_prepare_v2(db, p, q - p, &statement, &z_tail); + status = sqlite3_prepare_v2(db, p, statement_len, &statement, &z_tail); #else - status = sqlite3_prepare(db, p, q - p, &statement, &z_tail); + status = sqlite3_prepare(db, p, statement_len, &statement, &z_tail); #endif if (sql_check_error(db, status) != RLM_SQL_OK) { @@ -342,6 +334,11 @@ static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, char const *filename) return -1; } + /* + * No SQL statement was found + */ + if (!statement) break; + status = sqlite3_step(statement); if (sql_check_error(db, status) != RLM_SQL_OK) { sql_print_error(db, status, "Failed executing statement %i", statement_cnt); @@ -358,7 +355,7 @@ static int sql_loadfile(TALLOC_CTX *ctx, sqlite3 *db, char const *filename) } statement_cnt++; - p = q + 1; + p = z_tail; } talloc_free(buffer); diff --git a/src/modules/rlm_sql_map/rlm_sql_map.c b/src/modules/rlm_sql_map/rlm_sql_map.c index b6a27e5..28e2634 100644 --- a/src/modules/rlm_sql_map/rlm_sql_map.c +++ b/src/modules/rlm_sql_map/rlm_sql_map.c @@ -32,8 +32,6 @@ RCSID("$Id$") #include <rlm_sql.h> -#define MAX_QUERY_LEN 2048 - typedef struct rlm_sql_map_t { char const *sql_instance_name; //!< Instance of SQL module to use, //!< usually just 'sql'. diff --git a/src/modules/rlm_sqlcounter/rlm_sqlcounter.c b/src/modules/rlm_sqlcounter/rlm_sqlcounter.c index 5987ae6..cd41ac4 100644 --- a/src/modules/rlm_sqlcounter/rlm_sqlcounter.c +++ b/src/modules/rlm_sqlcounter/rlm_sqlcounter.c @@ -134,7 +134,7 @@ static int find_next_reset(rlm_sqlcounter_t *inst, REQUEST *request, time_t time } num = atoi(inst->reset); - DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last); + DEBUG3("rlm_sqlcounter: num=%d, last=%c",num,last); } if (strcmp(inst->reset, "hourly") == 0 || last == 'h') { @@ -181,10 +181,10 @@ static int find_next_reset(rlm_sqlcounter_t *inst, REQUEST *request, time_t time if (len == 0) *sNextTime = '\0'; if (is_monthly) { - DEBUG("rlm_sqlcounter: Current Time: %" PRId64 " [%s], Next reset %" PRId64 " [%s], Reset day [%d]", + DEBUG2("rlm_sqlcounter: Current Time: %" PRId64 " [%s], Next reset %" PRId64 " [%s], Reset day [%d]", (int64_t) timeval, sCurrentTime, (int64_t) inst->reset_time, sNextTime, inst->reset_day); } else { - DEBUG("rlm_sqlcounter: Current Time: %" PRId64 " [%s], Next reset %" PRId64 " [%s]", + DEBUG2("rlm_sqlcounter: Current Time: %" PRId64 " [%s], Next reset %" PRId64 " [%s]", (int64_t) timeval, sCurrentTime, (int64_t) inst->reset_time, sNextTime); } return ret; @@ -220,7 +220,7 @@ static int find_prev_reset(rlm_sqlcounter_t *inst, time_t timeval) if (!isalpha((uint8_t) last)) last = 'd'; num = atoi(inst->reset); - DEBUG("rlm_sqlcounter: num=%d, last=%c",num,last); + DEBUG3("rlm_sqlcounter: num=%d, last=%c",num,last); } if (strcmp(inst->reset, "hourly") == 0 || last == 'h') { diff --git a/src/modules/rlm_sqlippool/rlm_sqlippool.c b/src/modules/rlm_sqlippool/rlm_sqlippool.c index 22e9381..cf8d9d0 100644 --- a/src/modules/rlm_sqlippool/rlm_sqlippool.c +++ b/src/modules/rlm_sqlippool/rlm_sqlippool.c @@ -66,30 +66,15 @@ typedef struct rlm_sqlippool_t { char const *pool_check; //!< Query to check for the existence of the pool. - /* Start sequence */ - char const *start_begin; //!< SQL query to begin. - char const *start_update; //!< SQL query to update an IP entry. - char const *start_commit; //!< SQL query to commit. - - /* Alive sequence */ - char const *alive_begin; //!< SQL query to begin. - char const *alive_update; //!< SQL query to update an IP entry. - char const *alive_commit; //!< SQL query to commit. - - /* Stop sequence */ - char const *stop_begin; //!< SQL query to begin. - char const *stop_clear; //!< SQL query to clear an IP. - char const *stop_commit; //!< SQL query to commit. - - /* On sequence */ - char const *on_begin; //!< SQL query to begin. - char const *on_clear; //!< SQL query to clear an entire NAS. - char const *on_commit; //!< SQL query to commit. - - /* Off sequence */ - char const *off_begin; //!< SQL query to begin. - char const *off_clear; //!< SQL query to clear an entire NAS. - char const *off_commit; //!< SQL query to commit. + char const *start_update; //!< SQL query run on Accounting Start + + char const *alive_update; //!< SQL query run on Accounting Interim-Update + + char const *stop_clear; //!< SQL query run on Accounting Stop + + char const *on_clear; //!< SQL query run on Accounting On + + char const *off_clear; //!< SQL query run on Accounting Off /* Logging Section */ char const *log_exists; //!< There was an ip address already assigned. @@ -97,10 +82,6 @@ typedef struct rlm_sqlippool_t { char const *log_clear; //!< We successfully deallocated ip address from pool. char const *log_failed; //!< Failed to allocate ip from the pool. char const *log_nopool; //!< There was no Framed-IP-Address but also no Pool-Name. - - /* Reserved to handle 255.255.255.254 Requests */ - char const *defaultpool; //!< Default Pool-Name if there is none in the check items. - } rlm_sqlippool_t; static CONF_PARSER message_config[] = { @@ -131,10 +112,6 @@ static CONF_PARSER module_config[] = { { "pool-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, pool_name), NULL }, { "pool_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, pool_name), "Pool-Name" }, - { "default-pool", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sqlippool_t, defaultpool), NULL }, - { "default_pool", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, defaultpool), "main_pool" }, - - { "ipv6", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, ipv6), NULL}, { "allow_duplicates", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sqlippool_t, allow_duplicates), NULL}, { "attribute_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sqlippool_t, attribute_name), NULL}, @@ -168,55 +145,21 @@ static CONF_PARSER module_config[] = { { "pool_check", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, pool_check), "" }, - { "start-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_begin), NULL }, - { "start_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_begin), "" }, - { "start-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_update), NULL }, { "start_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, start_update), "" }, - { "start-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, start_commit), NULL }, - { "start_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, start_commit), "" }, - - - { "alive-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_begin), NULL }, - { "alive_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_begin), "" }, - { "alive-update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_update), NULL }, { "alive_update", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, alive_update), "" }, - { "alive-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, alive_commit), NULL }, - { "alive_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, alive_commit), "" }, - - - { "stop-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_begin), NULL }, - { "stop_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_begin), "" }, - { "stop-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_clear), NULL }, { "stop_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, stop_clear), "" }, - { "stop-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, stop_commit), NULL }, - { "stop_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, stop_commit), "" }, - - - { "on-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_begin), NULL }, - { "on_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_begin), "" }, - { "on-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_clear), NULL }, { "on_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, on_clear), "" }, - { "on-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, on_commit), NULL }, - { "on_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, on_commit), "" }, - - - { "off-begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_begin), NULL }, - { "off_begin", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_begin), "" }, - { "off-clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_clear), NULL }, { "off_clear", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT , rlm_sqlippool_t, off_clear), "" }, - { "off-commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_DEPRECATED, rlm_sqlippool_t, off_commit), NULL }, - { "off_commit", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sqlippool_t, off_commit), "" }, - { "messages", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) message_config }, CONF_PARSER_TERMINATOR }; @@ -405,7 +348,7 @@ static int CC_HINT(nonnull (1, 3, 4, 5)) sqlippool_query1(char *out, int outlen, } if (!(*handle)->row) { - REDEBUG("SQL query did not return any results"); + RDEBUG2("SQL query did not return any results"); goto finish; } @@ -416,7 +359,7 @@ static int CC_HINT(nonnull (1, 3, 4, 5)) sqlippool_query1(char *out, int outlen, rlen = strlen((*handle)->row[0]); if (rlen >= outlen) { - RDEBUG("insufficient string space"); + REDEBUG("The first column of the result was too long (%d)", rlen); goto finish; } @@ -757,9 +700,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque static int mod_accounting_start(rlm_sql_handle_t **handle, rlm_sqlippool_t *inst, REQUEST *request) { - DO(start_begin); DO(start_update); - DO(start_commit); return RLM_MODULE_OK; } @@ -769,9 +710,7 @@ static int mod_accounting_alive(rlm_sql_handle_t **handle, { int affected; - DO(alive_begin); DO_AFFECTED(alive_update, affected); - DO(alive_commit); return (affected == 0 ? RLM_MODULE_NOTFOUND : RLM_MODULE_OK); } @@ -779,9 +718,7 @@ static int mod_accounting_alive(rlm_sql_handle_t **handle, static int mod_accounting_stop(rlm_sql_handle_t **handle, rlm_sqlippool_t *inst, REQUEST *request) { - DO(stop_begin); DO(stop_clear); - DO(stop_commit); return do_logging(request, inst->log_clear, RLM_MODULE_OK); } @@ -789,9 +726,7 @@ static int mod_accounting_stop(rlm_sql_handle_t **handle, static int mod_accounting_on(rlm_sql_handle_t **handle, rlm_sqlippool_t *inst, REQUEST *request) { - DO(on_begin); DO(on_clear); - DO(on_commit); return RLM_MODULE_OK; } @@ -799,9 +734,7 @@ static int mod_accounting_on(rlm_sql_handle_t **handle, static int mod_accounting_off(rlm_sql_handle_t **handle, rlm_sqlippool_t *inst, REQUEST *request) { - DO(off_begin); DO(off_clear); - DO(off_commit); return RLM_MODULE_OK; } diff --git a/src/modules/rlm_totp/Makefile b/src/modules/rlm_totp/Makefile index 5ad2ae9..595cea6 100644 --- a/src/modules/rlm_totp/Makefile +++ b/src/modules/rlm_totp/Makefile @@ -22,7 +22,7 @@ freeradius-devel: # ./totp totp <time> <sha1key> <8-character-challenge> # totp: rlm_totp.c | src freeradius-devel - @$(CC) -DTESTING $(CFLAGS) $(OPENSSL_CPPFLAGS) -o $@ $(LDFLAGS) $(LIBS) ../../../build/lib/.libs/libfreeradius-radius.a rlm_totp.c + @$(CC) -DTESTING $(CFLAGS) $(CPPFLAGS) $(OPENSSL_CPPFLAGS) -o $@ $(LDFLAGS) $(LIBS) ../../../build/lib/.libs/libfreeradius-radius.a rlm_totp.c # # Test vectors from RFC 6238, Appendix B diff --git a/src/modules/rlm_totp/rlm_totp.c b/src/modules/rlm_totp/rlm_totp.c index 1459716..d58e1ee 100644 --- a/src/modules/rlm_totp/rlm_totp.c +++ b/src/modules/rlm_totp/rlm_totp.c @@ -26,9 +26,68 @@ RCSID("$Id$") #include <freeradius-devel/radiusd.h> #include <freeradius-devel/modules.h> +#include <freeradius-devel/dlist.h> #include <freeradius-devel/rad_assert.h> -#define TIME_STEP (30) +#include <ctype.h> + +typedef struct { + uint8_t const *key; + size_t keylen; + char const *passwd; + time_t when; + bool unlisted; + void *instance; + fr_dlist_t dlist; +} totp_dedup_t; + +#ifdef HAVE_PTHREAD_H +#include <pthread.h> + +#define PTHREAD_MUTEX_LOCK(_x) pthread_mutex_lock(&((_x)->mutex)) +#define PTHREAD_MUTEX_UNLOCK(_x) pthread_mutex_unlock(&((_x)->mutex)) +#else +#define PTHREAD_MUTEX_LOCK(_x) +#define PTHREAD_MUTEX_UNLOCK(_x) +#endif + + +/* Define a structure for the configuration variables */ +typedef struct rlm_totp_t { + char const *name; //!< name of this instance */ + uint32_t time_step; //!< seconds + uint32_t otp_length; //!< forced to 6 or 8 + uint32_t lookback_steps; //!< number of steps to look back + uint32_t lookback_interval; //!< interval in seconds between steps + uint32_t lookforward_steps; //!< number of steps to look forwards + rbtree_t *dedup_tree; + fr_dlist_t dedup_list; +#ifdef HAVE_PTHREAD_H + pthread_mutex_t mutex; +#endif +} rlm_totp_t; + +#ifndef TESTING +/* Map configuration file names to internal variables */ +static const CONF_PARSER module_config[] = { + { "time_step", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, time_step), "30" }, + { "otp_length", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, otp_length), "6" }, + { "lookback_steps", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, lookback_steps), "1" }, + { "lookback_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, lookback_interval), "30" }, + { "lookforward_steps", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, lookforward_steps), "0" }, + CONF_PARSER_TERMINATOR +}; + +#define TIME_STEP (inst->time_step) +#define OTP_LEN (inst->otp_length) +#define BACK_STEPS (steps) +#define BACK_STEP_SECS (inst->lookback_interval) +#else +#define TIME_STEP (30) +#define OTP_LEN (8) +#define BACK_STEPS (1) +#define BACK_STEP_SECS (30) +#endif /* * RFC 4648 base32 decoding. @@ -110,7 +169,7 @@ static ssize_t base32_decode(uint8_t *out, size_t outlen, char const *in) * Will get converted to * * 11111222 22333334 44445555 56666677 77788888 - */ + */ for (p = b = out; p < end; p += 8) { b[0] = p[0] << 3; b[0] |= p[1] >> 2; @@ -142,14 +201,112 @@ static ssize_t base32_decode(uint8_t *out, size_t outlen, char const *in) return b - out; } + #ifndef TESTING -#define LEN 6 -#define PRINT "%06u" -#define DIV 1000000 -#else -#define LEN 8 -#define PRINT "%08u" -#define DIV 100000000 +#define TESTING_UNUSED + +#else /* TESTING */ +#undef RDEBUG3 +#define RDEBUG3(fmt, ...) printf(fmt "\n", ## __VA_ARGS__) +#define TESTING_UNUSED UNUSED +#endif + +#ifndef TESTING +static int mod_bootstrap(CONF_SECTION *conf, void *instance) +{ + rlm_totp_t *inst = instance; + + inst->name = cf_section_name2(conf); + if (!inst->name) { + inst->name = cf_section_name1(conf); + } + + return 0; +} + +static int dedup_cmp(void const *one, void const *two) +{ + int rcode; + totp_dedup_t const *a = one; + totp_dedup_t const *b = two; + + if (a->keylen < b->keylen) return -1; + if (a->keylen > b->keylen) return +1; + + rcode = memcmp(a->key , b->key, a->keylen); + if (rcode != 0) return rcode; + + /* + * The user can enter multiple keys + */ + return strcmp(a->passwd, b->passwd); +} + +static void dedup_free(void *data) +{ + totp_dedup_t *dedup = data; +#ifdef HAVE_PTHREAD_H + rlm_totp_t *inst = dedup->instance; +#endif + + if (!dedup->unlisted) { + PTHREAD_MUTEX_LOCK(inst); + fr_dlist_entry_unlink(&dedup->dlist); + PTHREAD_MUTEX_UNLOCK(inst); + } + + free(dedup); +} + +/* + * Do any per-module initialization that is separate to each + * configured instance of the module. e.g. set up connections + * to external databases, read configuration files, set up + * dictionary entries, etc. + * + * If configuration information is given in the config section + * that must be referenced in later calls, store a handle to it + * in *instance otherwise put a null pointer there. + */ +static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) +{ + rlm_totp_t *inst = instance; + + FR_INTEGER_BOUND_CHECK("time_step", inst->time_step, >=, 5); + FR_INTEGER_BOUND_CHECK("time_step", inst->time_step, <=, 120); + + FR_INTEGER_BOUND_CHECK("lookback_steps", inst->lookback_steps, >=, 1); + FR_INTEGER_BOUND_CHECK("lookback_steps", inst->lookback_steps, <=, 10); + + FR_INTEGER_BOUND_CHECK("lookforward_steps", inst->lookforward_steps, <=, 10); + + FR_INTEGER_BOUND_CHECK("lookback_interval", inst->lookback_interval, <=, inst->time_step); + + FR_INTEGER_BOUND_CHECK("otp_length", inst->otp_length, >=, 6); + FR_INTEGER_BOUND_CHECK("otp_length", inst->otp_length, <=, 8); + + if (inst->otp_length == 7) inst->otp_length = 8; + + inst->dedup_tree = rbtree_create(instance, dedup_cmp, dedup_free, 0); + if (!inst->dedup_tree) return -1; + + fr_dlist_entry_init(&inst->dedup_list); +#ifdef HAVE_PTHREAD_H + (void) pthread_mutex_init(&inst->mutex, NULL); +#endif + + return 0; +} + +#ifdef HAVE_PTHREAD_H +static int mod_detach(void *instance) +{ + rlm_totp_t *inst = instance; + + pthread_mutex_destroy(&inst->mutex); + return 0; +} +#endif #endif /* @@ -159,8 +316,14 @@ static ssize_t base32_decode(uint8_t *out, size_t outlen, char const *in) * for 8-character challenges, and not for 6 character * challenges! */ -static int totp_cmp(time_t now, uint8_t const *key, size_t keylen, char const *totp) +static int totp_cmp(TESTING_UNUSED REQUEST *request, time_t now, uint8_t const *key, size_t keylen, char const *totp, TESTING_UNUSED void *instance) { +#ifndef TESTING + rlm_totp_t *inst = instance; + uint32_t steps = inst->lookback_steps > inst->lookforward_steps ? inst->lookback_steps : inst->lookforward_steps; +#endif + time_t diff, then; + unsigned int i; uint8_t offset; uint32_t challenge; uint64_t padded; @@ -168,59 +331,153 @@ static int totp_cmp(time_t now, uint8_t const *key, size_t keylen, char const *t uint8_t data[8]; uint8_t digest[SHA1_DIGEST_LENGTH]; - padded = ((uint64_t) now) / TIME_STEP; - data[0] = padded >> 56; - data[1] = padded >> 48; - data[2] = padded >> 40; - data[3] = padded >> 32; - data[4] = padded >> 24; - data[5] = padded >> 16; - data[6] = padded >> 8; - data[7] = padded & 0xff; - /* - * Encrypt the network order time with the key. + * First try to authenticate against the current OTP, then step + * back in increments of BACK_STEP_SECS, up to BACK_STEPS times, + * to authenticate properly in cases of long transit delay, as + * described in RFC 6238, secion 5.2. */ - fr_hmac_sha1(digest, data, 8, key, keylen); - /* - * Take the least significant 4 bits. - */ - offset = digest[SHA1_DIGEST_LENGTH - 1] & 0x0f; + for (i = 0, diff = 0; i <= BACK_STEPS; i++, diff += BACK_STEP_SECS) { +#ifndef TESTING + if (i > inst->lookback_steps) goto forwards; +#endif + then = now - diff; +#ifndef TESTING + repeat: +#endif + padded = (uint64_t) then / TIME_STEP; + data[0] = padded >> 56; + data[1] = padded >> 48; + data[2] = padded >> 40; + data[3] = padded >> 32; + data[4] = padded >> 24; + data[5] = padded >> 16; + data[6] = padded >> 8; + data[7] = padded & 0xff; + + /* + * Encrypt the network order time with the key. + */ + fr_hmac_sha1(digest, data, 8, key, keylen); + + /* + * Take the least significant 4 bits. + */ + offset = digest[SHA1_DIGEST_LENGTH - 1] & 0x0f; + + /* + * Grab the 32bits at "offset", and drop the high bit. + */ + challenge = (digest[offset] & 0x7f) << 24; + challenge |= digest[offset + 1] << 16; + challenge |= digest[offset + 2] << 8; + challenge |= digest[offset + 3]; + + /* + * The token is the last 6 digits in the number (or 8 for testing).. + */ + snprintf(buffer, sizeof(buffer), ((OTP_LEN == 6) ? "%06u" : "%08u"), + challenge % ((OTP_LEN == 6) ? 1000000 : 100000000)); + + RDEBUG3("Now: %zu, Then: %zu", (size_t) now, (size_t) then); + RDEBUG3("Expected %s", buffer); + RDEBUG3("Received %s", totp); + + if (rad_digest_cmp((uint8_t const *) buffer, (uint8_t const *) totp, OTP_LEN) == 0) return 0; + +#ifndef TESTING + /* + * We've tested backwards, now do the equivalent time slot forwards + */ + if ((then < now) && (i <= inst->lookforward_steps)) { + forwards: + then = now + diff; + goto repeat; + } +#endif + } + return 1; +} + +#ifndef TESTING + +static inline CC_HINT(nonnull) totp_dedup_t *fr_dlist_head(fr_dlist_t const *head) +{ + if (head->prev == head) return NULL; + + return (totp_dedup_t *) (((uintptr_t) head->next) - offsetof(totp_dedup_t, dlist)); +} + + +static bool totp_reused(void *instance, time_t now, uint8_t const *key, size_t keylen, char const *passwd) +{ + rlm_totp_t *inst = instance; + totp_dedup_t *dedup, my_dedup; + + my_dedup.key = key; + my_dedup.keylen = keylen; + my_dedup.passwd = passwd; + + PTHREAD_MUTEX_LOCK(inst); /* - * Grab the 32bits at "offset", and drop the high bit. + * Expire the oldest entries before searching for an entry in the tree. */ - challenge = (digest[offset] & 0x7f) << 24; - challenge |= digest[offset + 1] << 16; - challenge |= digest[offset + 2] << 8; - challenge |= digest[offset + 3]; + while (true) { + dedup = fr_dlist_head(&inst->dedup_list); + if (!dedup) break; + + if ((now - dedup->when) < (inst->lookback_steps * inst->lookback_interval)) break; + + dedup->unlisted = true; + fr_dlist_entry_unlink(&dedup->dlist); + (void) rbtree_deletebydata(inst->dedup_tree, dedup); + } /* - * The token is the last 6 digits in the number. + * Was this key and TOTP reused? */ - snprintf(buffer, sizeof(buffer), PRINT, challenge % DIV); + dedup = rbtree_finddata(inst->dedup_tree, &my_dedup); + if (dedup) { + PTHREAD_MUTEX_UNLOCK(inst); + return true; + } - return rad_digest_cmp((uint8_t const *) buffer, (uint8_t const *) totp, LEN); -} + dedup = calloc(sizeof(*dedup), 1); + if (!dedup) { + PTHREAD_MUTEX_UNLOCK(inst); + return false; + } -#ifndef TESTING + dedup->key = key; + dedup->keylen = keylen; + dedup->passwd = passwd; + dedup->when = now; + dedup->instance = inst; + + fr_dlist_insert_tail(&inst->dedup_list, &dedup->dlist); + (void) rbtree_insert(inst->dedup_tree, dedup); + PTHREAD_MUTEX_UNLOCK(inst); + + return false; +} /* * Do the authentication */ -static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQUEST *request) +static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) { VALUE_PAIR *vp, *password; uint8_t const *key; size_t keylen; uint8_t buffer[80]; /* multiple of 5*8 characters */ - + uint64_t now = time(NULL); password = fr_pair_find_by_num(request->packet->vps, PW_TOTP_PASSWORD, 0, TAG_ANY); if (!password) return RLM_MODULE_NOOP; - if (password->vp_length != 6) { + if ((password->vp_length != 6) && (password->vp_length != 8)) { RDEBUG("TOTP-Password has incorrect length %d", (int) password->vp_length); return RLM_MODULE_FAIL; } @@ -237,11 +494,13 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQU ssize_t len; vp = fr_pair_find_by_num(request->config, PW_TOTP_SECRET, 0, TAG_ANY); - if (!vp) return RLM_MODULE_NOOP; - + if (!vp) { + RDEBUG("TOTP mod_authenticate() did not receive a TOTP-Secret"); + return RLM_MODULE_NOOP; + } len = base32_decode(buffer, sizeof(buffer), vp->vp_strvalue); if (len < 0) { - RDEBUG("TOTP-Secret cannot be decoded"); + REDEBUG("TOTP-Secret cannot be decoded"); return RLM_MODULE_FAIL; } @@ -249,9 +508,25 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQU keylen = len; } - if (totp_cmp(time(NULL), key, keylen, password->vp_strvalue) != 0) return RLM_MODULE_FAIL; + vp = fr_pair_find_by_num(request->config, PW_TOTP_TIME_OFFSET, 0, TAG_ANY); + if (vp && (vp->vp_signed > -600) && (vp->vp_signed < 600)) { + RDEBUG("Using TOTP-Time-Offset = %d", vp->vp_signed); + now += vp->vp_signed; + } + + if (totp_cmp(request, now, key, keylen, password->vp_strvalue, instance) == 0) { + /* + * Forbid using a key more than once. + */ + if (totp_reused(instance, now, key, keylen, password->vp_strvalue)) return RLM_MODULE_REJECT; + + return RLM_MODULE_OK; + } - return RLM_MODULE_OK; + /* + * Bad keys don't affect the cache. + */ + return RLM_MODULE_REJECT; } @@ -269,12 +544,24 @@ module_t rlm_totp = { .magic = RLM_MODULE_INIT, .name = "totp", .type = RLM_TYPE_THREAD_SAFE, + .inst_size = sizeof(rlm_totp_t), + .config = module_config, + .bootstrap = mod_bootstrap, + .instantiate = mod_instantiate, +#ifdef HAVE_PTHREAD_H + .detach = mod_detach, +#endif .methods = { [MOD_AUTHENTICATE] = mod_authenticate, }, }; #else /* TESTING */ +/* + * ./totp decode KEY_BASE32 + * + * ./totp totp now KEY TOTP + */ int main(int argc, char **argv) { size_t len; @@ -298,23 +585,31 @@ int main(int argc, char **argv) } /* - * TOTP <time> <key> <8-character-expected-token> + * TOTP <time> <key> <expected-token> */ if (strcmp(argv[1], "totp") == 0) { uint64_t now; if (argc < 5) return 0; - (void) sscanf(argv[2], "%llu", &now); + if (strcmp(argv[2], "now") == 0) { + now = time(NULL); + } else { + (void) sscanf(argv[2], "%llu", &now); + } + + printf ("=== Time = %llu, TIME_STEP = %d, BACK_STEPS = %d, BACK_STEP_SECS = %d ===\n", + now, TIME_STEP, BACK_STEPS, BACK_STEP_SECS); - if (totp_cmp((time_t) now, (uint8_t const *) argv[3], strlen(argv[3]), argv[4]) == 0) { - return 0; + if (totp_cmp(NULL, (time_t) now, (uint8_t const *) argv[3], + strlen(argv[3]), argv[4], NULL) == 0) { + return 0; } printf("Fail\n"); return 1; } - fprintf(stderr, "Unknown command argv[1]\n", argv[1]); + fprintf(stderr, "Unknown command %s\n", argv[1]); return 1; } #endif diff --git a/src/modules/rlm_unbound/rlm_unbound.c b/src/modules/rlm_unbound/rlm_unbound.c index dddc3bf..c5e837a 100644 --- a/src/modules/rlm_unbound/rlm_unbound.c +++ b/src/modules/rlm_unbound/rlm_unbound.c @@ -709,7 +709,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) return -1; } -static int mod_detach(UNUSED void *instance) +static int mod_detach(void *instance) { rlm_unbound_t *inst = instance; diff --git a/src/modules/rlm_unpack/rlm_unpack.c b/src/modules/rlm_unpack/rlm_unpack.c index dfdc81a..03cdb92 100644 --- a/src/modules/rlm_unpack/rlm_unpack.c +++ b/src/modules/rlm_unpack/rlm_unpack.c @@ -287,7 +287,7 @@ static ssize_t substring_xlat(UNUSED void *instance, REQUEST *request, /* * Trim whitespace */ - while (isspace((uint8_t) *p) && p++); + while (isspace((uint8_t) *p)) p++; /* * Find numeric parameters at the end. diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c index d2125eb..eb83e25 100644 --- a/src/modules/rlm_wimax/rlm_wimax.c +++ b/src/modules/rlm_wimax/rlm_wimax.c @@ -35,20 +35,13 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include <freeradius-devel/openssl3.h> #define WIMAX_EPSAKA_RAND_SIZE 16 -#define WIMAX_EPSAKA_KI_SIZE 16 -#define WIMAX_EPSAKA_OPC_SIZE 16 -#define WIMAX_EPSAKA_AMF_SIZE 2 #define WIMAX_EPSAKA_SQN_SIZE 6 -#define WIMAX_EPSAKA_MAC_A_SIZE 8 -#define WIMAX_EPSAKA_MAC_S_SIZE 8 #define WIMAX_EPSAKA_XRES_SIZE 8 #define WIMAX_EPSAKA_CK_SIZE 16 #define WIMAX_EPSAKA_IK_SIZE 16 #define WIMAX_EPSAKA_AK_SIZE 6 -#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6 #define WIMAX_EPSAKA_KK_SIZE 32 #define WIMAX_EPSAKA_KS_SIZE 14 -#define WIMAX_EPSAKA_PLMN_SIZE 3 #define WIMAX_EPSAKA_KASME_SIZE 32 #define WIMAX_EPSAKA_AUTN_SIZE 16 #define WIMAX_EPSAKA_AUTS_SIZE 14 diff --git a/src/modules/rlm_yubikey/decrypt.c b/src/modules/rlm_yubikey/decrypt.c index 20b6df8..2bf4543 100644 --- a/src/modules/rlm_yubikey/decrypt.c +++ b/src/modules/rlm_yubikey/decrypt.c @@ -106,7 +106,7 @@ rlm_rcode_t rlm_yubikey_decrypt(rlm_yubikey_t *inst, REQUEST *request, char cons * Combine the two counter fields together so we can do * replay attack checks. */ - counter = (yubikey_counter(token.ctr) << 16) | token.use; + counter = (yubikey_counter(token.ctr) << 8) | token.use; vp = fr_pair_make(request->packet, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET); if (!vp) { diff --git a/src/modules/rlm_yubikey/rlm_yubikey.c b/src/modules/rlm_yubikey/rlm_yubikey.c index 83b7655..5bbed11 100644 --- a/src/modules/rlm_yubikey/rlm_yubikey.c +++ b/src/modules/rlm_yubikey/rlm_yubikey.c @@ -77,19 +77,16 @@ static ssize_t modhex2hex(char const *modhex, uint8_t *hex, size_t len) size_t i; char *c1, *c2; - for (i = 0; i < len; i++) { - if (modhex[i << 1] == '\0') { - break; - } + for (i = 0; i < len; i += 2) { + if (modhex[i] == '\0') break; /* * We only deal with whole bytes */ - if (modhex[(i << 1) + 1] == '\0') - return -1; + if (modhex[i + 1] == '\0') return -1; - if (!(c1 = memchr(modhextab, tolower((uint8_t) modhex[i << 1]), 16)) || - !(c2 = memchr(modhextab, tolower((uint8_t) modhex[(i << 1) + 1]), 16))) + if (!(c1 = memchr(modhextab, tolower((uint8_t) modhex[i]), 16)) || + !(c2 = memchr(modhextab, tolower((uint8_t) modhex[i + 1]), 16))) return -1; hex[i] = hextab[c1 - modhextab]; @@ -124,6 +121,10 @@ static ssize_t modhex_to_hex_xlat(UNUSED void *instance, REQUEST *request, char return -1; } + if (len < (ssize_t) outlen) { + out[len] = '\0'; + } + return len; } @@ -142,7 +143,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) } #endif - if (!cf_section_name2(conf)) return 0; + if (cf_section_name2(conf)) return 0; xlat_register("modhextohex", modhex_to_hex_xlat, NULL, inst); diff --git a/src/modules/stable b/src/modules/stable index 8abe0fe..d5aba18 100644 --- a/src/modules/stable +++ b/src/modules/stable @@ -6,6 +6,7 @@ rlm_counter rlm_detail rlm_dhcp rlm_digest +rlm_dpsk rlm_dynamic_clients rlm_eap rlm_exec diff --git a/src/tests/Makefile b/src/tests/Makefile index 2dab5b1..3fba18c 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -170,6 +170,7 @@ config/eap-test: $(RADDB_PATH)mods-available/eap config/eap-test-inner-tunnel -e 's/= inner-tunnel/= eap-test-inner-tunnel/;s/use_tunneled_reply = no/use_tunneled_reply = yes/' \ -e 's/enable = no/enable = yes/' \ -e 's/^\(.*\)persist_dir =/ persist_dir =/' \ + -e 's/#.*softfail =.*/softfail = yes/' \ -e 's/tls_min_version = "1.2"/tls_min_version = "1.0"/' \ -e '$(if $(TLS1_3),s/tls_max_version = "1.2"/tls_max_version = "1.3"/)' \ -e 's/cipher_list = "DEFAULT"/cipher_list = "DEFAULT${SECLEVEL}"/' \ @@ -185,6 +186,7 @@ radiusd.pid: test.conf tail -n 20 "$(TEST_PATH)/radius.log"; \ fi ${Q}echo "ok" + ${Q}echo "radiusd logging to $(TEST_PATH)/radius.log" # We can't make this depend on radiusd.pid, because then make will create # radiusd.pid when we make radiusd.kill, which we don't want. @@ -214,7 +216,23 @@ radiusd.kill: # ifneq "$(EAPOL_TEST)" "" EAP_FILES = eap-md5.conf -EAP_TLS_FILES = eap-ttls-pap.conf eap-ttls-mschapv2.conf peap-mschapv2.conf +EAP_FILES += eap-mschapv2.conf + +EAP_TLS_FILES = eap-tls.conf +EAP_TLS_FILES += eap-ttls-eap-gtc.conf +EAP_TLS_FILES += eap-ttls-eap-mschapv2.conf +EAP_TLS_FILES += eap-ttls-eap-tls.conf +EAP_TLS_FILES += eap-ttls-mschapv2.conf +EAP_TLS_FILES += eap-ttls-pap.conf +EAP_TLS_FILES += peap-client-mschapv2.conf +EAP_TLS_FILES += peap-eap-tls.conf +EAP_TLS_FILES += peap-gtc.conf +EAP_TLS_FILES += peap-mschapv2.conf + +#EAP_TLS_FILES += eap-fast.conf # disabled in default config +#EAP_TLS_FILES += eap-pwd.conf # disabled in default config +#EAP_TLS_FILES += eap-teap-mschapv2.conf # not configured in eapol_test + EAP_TLS_VERSIONS = 1.1 1.2 EAP_TLS_DISABLE_STRING = tls_disable_tlsv1_0=1 tls_disable_tlsv1_1=1 tls_disable_tlsv1_2=1 @@ -313,7 +331,7 @@ EAPOL_OK_FILES := $(sort $(addprefix $(BUILD_PATH)/tests/eap/,$(patsubst %.conf, tests.eap: $(EAPOL_OK_FILES) | radiusd.kill radiusd.pid else tests.eap: - ${Q}echo "EAPOL Tests is disabled" + ${Q}echo "EAP tests are disabled" endif # we have eapol_test built # kill the server (if it's running) @@ -326,3 +344,4 @@ tests.runtests: test.conf | radiusd.kill radiusd.pid ${Q}BIN_PATH="$(BIN_PATH)" PORT="$(PORT)" ./runtests.sh $(TESTS) tests: tests.runtests tests.eap + $(MAKE) radiusd.kill diff --git a/src/tests/all.mk b/src/tests/all.mk index 142772b..678ce59 100644 --- a/src/tests/all.mk +++ b/src/tests/all.mk @@ -4,15 +4,15 @@ SECRET := testing123 DICT_PATH := $(top_srcdir)/share # -# Include all of the autoconf definitions into the Make variable space +# Pull all of the autoconf stuff into here. # --include $(BUILD_DIR)/tests/keywords/autoconf.h.mk +$(BUILD_DIR)/tests/autoconf.h.mk: src/include/autoconf.h + @grep '^#define' $^ | sed 's/#define /AC_/;s/ / := /' > $@ # -# Pull all of the autoconf stuff into here. +# Include all of the autoconf definitions into the Make variable space # -$(BUILD_DIR)/tests/keywords/autoconf.h.mk: src/include/autoconf.h - @grep '^#define' $^ | sed 's/#define /AC_/;s/ / := /' > $@ +-include $(BUILD_DIR)/tests/autoconf.h.mk ###################################################################### # diff --git a/src/tests/eap-teap-mschapv2.conf b/src/tests/eap-teap-mschapv2.conf new file mode 100644 index 0000000..4b19bef --- /dev/null +++ b/src/tests/eap-teap-mschapv2.conf @@ -0,0 +1,21 @@ +# +# eapol_test -c eap-teap-mschapv2.conf -s testing123 +# +network={ + key_mgmt=IEEE8021X + eap=TEAP + anonymous_identity="anonymous" + identity="bob" + password="bob" + +# openssl_ciphers="DEFAULT@SECLEVEL=1" +# phase1="tls_disable_tlsv1_0=1 tls_disable_tlsv1_1=1 tls_disable_tlsv1_2=1 tls_disable_tlsv1_3=0" + phase2="autheap=MSCHAPV2" + +# phase1="tls_disable_session_ticket=0 fast_provisioning=2" +# phase1="fast_provisioning=0" + pac_file="blob://eap-fast-pac" + + ca_cert="../../raddb/certs/ca.pem" +# ca_cert="/etc/freeradius/certs/ca.pem" +} diff --git a/src/tests/eap-ttls-eap-gtc.conf b/src/tests/eap-ttls-eap-gtc.conf new file mode 100644 index 0000000..2796a99 --- /dev/null +++ b/src/tests/eap-ttls-eap-gtc.conf @@ -0,0 +1,17 @@ +# +# eapol_test -c eap-ttls-eap-gtc.conf -s testing123 +# +network={ + key_mgmt=IEEE8021X + eap=TTLS + + anonymous_identity="anonymous" + + identity="bob" + password="bob" + + phase1="" + phase2="autheap=GTC" + + ca_cert="../../raddb/certs/ca.pem" +} diff --git a/src/tests/keywords/all.mk b/src/tests/keywords/all.mk index 739b738..6535d35 100644 --- a/src/tests/keywords/all.mk +++ b/src/tests/keywords/all.mk @@ -21,15 +21,6 @@ $(BUILD_DIR)/tests/keywords: @mkdir -p $@ # -# Find which input files are needed by the tests -# strip out the ones which exist -# move the filenames to the build directory. -# -BOOTSTRAP_EXISTS := $(addprefix $(DIR)/,$(addsuffix .attrs,$(KEYWORD_FILES))) -BOOTSTRAP_NEEDS := $(filter-out $(wildcard $(BOOTSTRAP_EXISTS)),$(BOOTSTRAP_EXISTS)) -BOOTSTRAP := $(subst $(DIR),$(BUILD_DIR)/tests/keywords,$(BOOTSTRAP_NEEDS)) - -# # For each file, look for precursor test. # Ensure that each test depends on its precursors. # @@ -49,16 +40,34 @@ $(BUILD_DIR)/tests/keywords/depends.mk: $(addprefix $(DIR)/,$(KEYWORD_FILES)) | done # -# These ones get copied over from the default input +# For sheer laziness, allow "make test.keywords.foo" # -$(BOOTSTRAP): $(DIR)/default-input.attrs | $(BUILD_DIR)/tests/keywords - @cp $< $@ +define KEYWORD_TEST +tests.keywords.${1}: $(addprefix $(OUTPUT)/,${1}) + +tests.keywords.help: TEST_KEYWORDS_HELP += tests.keywords.${1} + +OUTPUT := $(BUILD_DIR)/tests/keywords # -# These ones get copied over from their original files +# Create the input attrs, either from the test-specific input, +# or from the default input. # -$(BUILD_DIR)/tests/keywords/%.attrs: $(DIR)/%.attrs | $(BUILD_DIR)/tests/keywords - @cp $< $@ +$(OUTPUT)/${1}: $(OUTPUT)/${1}.attrs | $(dir $(OUTPUT)/${1}) +$(OUTPUT)/${1}.attrs: | $(dir $(OUTPUT)/${1}) + +ifneq "$(wildcard src/tests/keywords/${1}.attrs)" "" +$(OUTPUT)/${1}.attrs: src/tests/keywords/${1}.attrs +else +$(OUTPUT)/${1}.attrs: src/tests/keywords/default-input.attrs +endif + @cp $$< $$@ +ifeq "${1}" "mschap" +$(OUTPUT)/${1}: rlm_mschap.la +endif + +endef +$(foreach x,$(KEYWORD_FILES),$(eval $(call KEYWORD_TEST,$x))) # # Don't auto-remove the files copied by the rule just above. diff --git a/src/tests/keywords/randstr b/src/tests/keywords/randstr new file mode 100644 index 0000000..6884cb0 --- /dev/null +++ b/src/tests/keywords/randstr @@ -0,0 +1,16 @@ +update request { + &Tmp-Octets-0 := "0x%{randstr:16h}" + &Tmp-String-0 := "%{randstr:16h}" +} + +if ("%{length:Tmp-Octets-0}" == 16) { + update reply { + Filter-Id := "filter" + } +} + +if ("%{length:Tmp-String-0}" == 32) { + update reply { + Filter-Id := "filter" + } +} diff --git a/src/tests/modules/date/all.mk b/src/tests/modules/date/all.mk new file mode 100644 index 0000000..90966df --- /dev/null +++ b/src/tests/modules/date/all.mk @@ -0,0 +1,3 @@ +# +# Test the "date" module +# diff --git a/src/tests/modules/date/date_xlat.attrs b/src/tests/modules/date/date_xlat.attrs new file mode 100644 index 0000000..ba430d2 --- /dev/null +++ b/src/tests/modules/date/date_xlat.attrs @@ -0,0 +1,13 @@ +# +# Input packet +# +Packet-Type = Access-Request +User-Name = 'Bob' +User-Password = 'Alice' +Tmp-Integer-6 = 0 + +# +# Expected answer +# +Response-Packet-Type == Access-Accept + diff --git a/src/tests/modules/date/date_xlat.unlang b/src/tests/modules/date/date_xlat.unlang new file mode 100644 index 0000000..c5501dd --- /dev/null +++ b/src/tests/modules/date/date_xlat.unlang @@ -0,0 +1,243 @@ +# +# Selection of tests for the %{time_since:} xlat +# +# Somewhat limited in what we can do here, as it bases its +# responses off the current system time. So we need to do some +# comparisons rather than actual value checks. +# + +# +# %{time_since:...} should never return 0 +# +update { + &Tmp-Integer64-0 := "%{time_since:s}" + &Tmp-Integer64-1 := "%{time_since:ms}" + &Tmp-Integer64-2 := "%{time_since:us}" +} + +if (&Tmp-Integer64-0 == 0 || &Tmp-Integer64-1 == 0 || &Tmp-Integer64-2 == 0) { + test_fail +} + +# +# and they should all be different +# +if (&Tmp-Integer64-0 == &Tmp-Integer64-1 || \ + &Tmp-Integer64-1 == &Tmp-Integer64-2 || \ + &Tmp-Integer64-2 == &Tmp-Integer64-0) { + test_fail +} + +# +# %c and %{time_since:s:0} should match +# +update { + &Tmp-Integer-9 := 0 +} + +update { + &Tmp-Integer-0 := "%c" + &Tmp-Integer-1 := "%{time_since:s 0}" + &Tmp-Integer-2 := "%{time_since:s &Tmp-Integer-9}" +} + +if (&Tmp-Integer-0 != &Tmp-Integer-1) { + if (&Tmp-Integer-0 != "%{expr:&Tmp-Integer-1 - 1}") { + # at a push, %{time_since:s 0} might be one second later, + # depending on when the test ran + test_fail + } +} + +if (&Tmp-Integer-1 != &Tmp-Integer-2) { + if (&Tmp-Integer-1 != "%{expr:&Tmp-Integer-2 - 1}") { + test_fail + } +} + +# +# If we run time_since 3 times, they should be the same or increasing +# +update { + &Tmp-Integer64-0 := "%{time_since:s 0}" +} + +update { + &Tmp-Integer64-1 := "%{time_since:s }" +} + +update { + &Tmp-Integer64-2 := "%{time_since:s}" +} + +if (&Tmp-Integer64-0 > &Tmp-Integer64-1 || \ + &Tmp-Integer64-1 > &Tmp-Integer64-2 || \ + &Tmp-Integer64-0 > &Tmp-Integer64-2) { + test_fail +} + +# +# It's way past the year 2020, so this should only fail if the +# computer's clock is very wrong... +# +if (&Tmp-Integer64-0 < 1600000000) { + test_fail +} + + +# +# Similar for milliseconds +# +update { + &Tmp-Integer64-3 := "%{time_since:ms &request:Tmp-Integer-6}" +} + +update { + &Tmp-Integer64-4 := "%{time_since:ms}" +} + +update { + &Tmp-Integer64-5 := "%{time_since:ms &Tmp-Integer-9}" +} + +if (&Tmp-Integer64-3 > &Tmp-Integer64-4 || \ + &Tmp-Integer64-4 > &Tmp-Integer64-5 || \ + &Tmp-Integer64-3 > &Tmp-Integer64-5) { + test_fail +} + + +# +# ...and microseconds +# +update session-state { + &Tmp-Integer-7 := 0 +} + +update { + &Tmp-Integer64-6 := "%{time_since:us &session-state:Tmp-Integer-7 }" +} + +update { + &Tmp-Integer64-7 := "%{time_since:us }" +} + +update { + &Tmp-Integer64-8 := "%{time_since:us}" +} + +if (&Tmp-Integer64-6 > &Tmp-Integer64-7 || \ + &Tmp-Integer64-7 > &Tmp-Integer64-8 || \ + &Tmp-Integer64-6 > &Tmp-Integer64-8) { + test_fail +} + +if ("%{expr:&Tmp-Integer64-7 - &Tmp-Integer64-6}" > 250) { + # you have a really slow computer if the time between + # getting these took more than 250us + test_fail +} + + +# +# Seconds component * 1000 must always be same or less than +# milliseconds, and microseconds. +# +if ("%{expr:%{time_since:s 0} * 1000}" > "%{time_since:ms 0}") { + test_fail +} + +if ("%{expr:%{time_since:ms 0} * 1000}" > "%{time_since:us 0}") { + test_fail +} + +if ("%{expr:%{time_since:s 0} * 1000000}" > "%{time_since:us 0}") { + test_fail +} + + +# +# Test for some errors +# + +# missing time base +update { + &Tmp-Integer-0 := "%{time_since:}" +} + +if (!(&Module-Failure-Message[*] == 'Time base (ms, us, s) missing in time_since xlat')) { + test_fail +} + +update { + &Module-Failure-Message !* ANY +} + + +# invalid time base +update { + &Tmp-Integer-0 := "%{time_since:bob}" +} + +if (!(&Module-Failure-Message[*] == 'Time base (ms, us, s) missing in time_since xlat')) { + test_fail +} + +update { + &Module-Failure-Message !* ANY +} + + +# negative values +update { + &Tmp-Integer-0 := "%{time_since:ms -1234}" +} + +if (!(&Module-Failure-Message[*] == 'time_since xlat only accepts positive integers')) { + test_fail +} + +update { + &Module-Failure-Message !* ANY +} + + +# invalid attribute +update { + &Tmp-Integer-0 := "%{time_since:us &Test-Non-Existant-Attr}" +} + +if (!(&Module-Failure-Message[*] == 'Unable to parse attribute in time_since xlat')) { + test_fail +} + +update { + &Module-Failure-Message !* ANY +} + + +# silly text +update { + &Tmp-Integer-0 := "%{time_since:us test random text}" +} + +if (!(&Module-Failure-Message[*] == 'Failed parsing "test random text" as integer')) { + test_fail +} + +update { + &Module-Failure-Message !* ANY +} + + +# attribute not in list (warning, so check output) +update { + &Tmp-Integer-0 := "%{time_since:us &reply:Tmp-Integer-4}" +} + +if (&Tmp-Integer-0 != 0) { + test_fail +} + + +test_pass diff --git a/src/tests/modules/date/module.conf b/src/tests/modules/date/module.conf new file mode 100644 index 0000000..cb7ef07 --- /dev/null +++ b/src/tests/modules/date/module.conf @@ -0,0 +1,3 @@ +#date unit test config +date { +} diff --git a/src/tests/modules/dpsk/pmk.txt b/src/tests/modules/dpsk/pmk.txt new file mode 100644 index 0000000..db3b6bd --- /dev/null +++ b/src/tests/modules/dpsk/pmk.txt @@ -0,0 +1,7 @@ +User-Name = "cae78dfa6504" +User-Password = "cae78dfa6504" +Called-Station-Id = "5c:df:89:11L3bL3c:SSID" +Calling-Station-Id = "ca:e7:8d:fa:65:04" +FreeRADIUS-802.1X-Anonce = 0x43426fd6469d4254eb0d5ba449eb9895360894f1948cece9196751336d4c5daf +FreeRADIUS-802.1X-EAPoL-Key-Msg = 0x0103007502010a00000000000000000001b16a8514b84d7843e53754f5c9131cb203fbe8277dbf216d6e87fd6e30b0577a0000000000000000000000000000000000000000000000000000000000000000dc81aec5a05ee8aa21a52947041fd2fc001630140100000fac040100000fac040100000fac028000 +Class = 0xd6175aed517504c40b8831d7ce7b7d1fe24c65ce0f92c2816ca14ba7acb47b13 diff --git a/src/tests/modules/dpsk/psk.txt b/src/tests/modules/dpsk/psk.txt new file mode 100644 index 0000000..f4e584f --- /dev/null +++ b/src/tests/modules/dpsk/psk.txt @@ -0,0 +1,9 @@ +User-Name = "8ab3a0ebd5e5" +User-Password = "8ab3a0ebd5e5" +NAS-IP-Address = 127.0.0.1 +Called-Station-Id = "34:ef:b6:af:48:9e:Andrena_39_Lincoln" +Calling-Station-Id = "8a:b3:a0:eb:d5:e5" +NAS-Identifier = "34efb6af489e" +FreeRADIUS-802.1X-Anonce = 0x4df70a4285c5c61f177cdbfc29d7e3cac94167f6101f1bcab420dd50c4f8809d +FreeRADIUS-802.1X-EAPoL-Key-Msg = 0x0203007502010a00100000000000000001c3bb319516614aacfb44e933bf1671131fb1856e5b2721952d414ce3f5aa312b000000000000000000000000000000000000000000000000000000000000000035cddcedad0dfb6a12a2eca55c17c323001630140100000fac040100000fac040100000fac028c00 +Filter-ID = "Pancakes1124" diff --git a/src/tests/modules/dpsk/radiusd.conf b/src/tests/modules/dpsk/radiusd.conf new file mode 100644 index 0000000..c4d1782 --- /dev/null +++ b/src/tests/modules/dpsk/radiusd.conf @@ -0,0 +1,15 @@ + rewrite_called_station_id + dpsk + if (ok) { + if (&Class) { + update control { + &Pairwise-Master-Key := &Class + } + } + elsif (&Filter-ID) { + update control { + &Pre-Shared-Key := &Filter-ID + } + } + } + diff --git a/src/tests/modules/files/authorize b/src/tests/modules/files/authorize index b85f6a2..6ef314e 100644 --- a/src/tests/modules/files/authorize +++ b/src/tests/modules/files/authorize @@ -90,3 +90,13 @@ addcontrol Cleartext-Password := "testing123", Reply-Message := "success1" Fall-Through = yes addcontrol Reply-Message += "success2" + + +# +# Doesn't match +# +DEFAULT Framed-IP-Address == 192.0.2.1 + Reply-Message += "unexpected match in DEFAULT" + +DEFAULT + Reply-Message = "empty DEFAULT" diff --git a/src/tests/modules/files/empty_default.attrs b/src/tests/modules/files/empty_default.attrs new file mode 100644 index 0000000..428fa1e --- /dev/null +++ b/src/tests/modules/files/empty_default.attrs @@ -0,0 +1,11 @@ +# +# Input packet +# +User-Name = "empty_default" +User-Password = "testing123" + +# +# Expected answer +# +Response-Packet-Type == Access-Accept +Reply-Message == "empty DEFAULT" diff --git a/src/tests/modules/files/empty_default.unlang b/src/tests/modules/files/empty_default.unlang new file mode 100644 index 0000000..ac4aa4d --- /dev/null +++ b/src/tests/modules/files/empty_default.unlang @@ -0,0 +1,9 @@ +# +# Run the "files" module +# +files + +update control { + Auth-Type := Accept +} + diff --git a/src/tests/modules/yubikey/all.mk b/src/tests/modules/yubikey/all.mk new file mode 100644 index 0000000..b62dbc2 --- /dev/null +++ b/src/tests/modules/yubikey/all.mk @@ -0,0 +1,3 @@ +# +# Test the "yubikey" module xlat +# diff --git a/src/tests/modules/yubikey/module.conf b/src/tests/modules/yubikey/module.conf new file mode 100644 index 0000000..a9549f3 --- /dev/null +++ b/src/tests/modules/yubikey/module.conf @@ -0,0 +1,11 @@ +yubikey { + + id_length = 12 + + split = yes + + decrypt = yes + + validate = no + +} diff --git a/src/tests/modules/yubikey/yubikey_auth.attrs b/src/tests/modules/yubikey/yubikey_auth.attrs new file mode 100644 index 0000000..d1fa1de --- /dev/null +++ b/src/tests/modules/yubikey/yubikey_auth.attrs @@ -0,0 +1,11 @@ +# +# Input packet +# +Packet-Type = Access-Request +User-Name = "bob" +User-Password = "helloddddgciilcjkjhlifidginuirlhgidcvbfnutjnibldi" + +# +# Expected answer +# +Response-Packet-Type == Access-Accept diff --git a/src/tests/modules/yubikey/yubikey_auth.unlang b/src/tests/modules/yubikey/yubikey_auth.unlang new file mode 100644 index 0000000..ae9f534 --- /dev/null +++ b/src/tests/modules/yubikey/yubikey_auth.unlang @@ -0,0 +1,56 @@ +# Call yubikey module to split OTP from password +yubikey + +if !(&User-Password == 'hello') { + test_fail +} +if !(&Yubikey-OTP) { + test_fail +} +if !(&Yubikey-Public-Id == 'ddddgciilcjk') { + test_fail +} + +update control { + &Yubikey-Counter := 1 + &Yubikey-Key := 0xb8c56af07ff79b2230e04ab8891784ce +} + +# Call module in authenticate mode to decrypt OTP +yubikey.authenticate + +# Check all the attributes have been created +if !(&Yubikey-Private-Id == 0x1dfc67f97828) { + test_fail +} +if !(&Yubikey-Timestamp) { + test_fail +} +if !(&Yubikey-Counter == 258) { + test_fail +} +if !(&Yubikey-Random) { + test_fail +} + + +# Increase the known "counter" value to detect a replay attack +update { + &control:Yubikey-Counter := &Yubikey-Counter +} + +yubikey.authenticate { + reject = 1 +} + +# Replay attack should result in a reject and a suitable module failure +if !(reject) { + test_fail +} +debug_all + +if !(&Module-Failure-Message == 'yubikey: Replay attack detected! Counter value 258, is lt or eq to last known counter value 258') { + test_fail +} + +test_pass diff --git a/src/tests/modules/yubikey/yubikey_xlat.attrs b/src/tests/modules/yubikey/yubikey_xlat.attrs new file mode 100644 index 0000000..1cce1c5 --- /dev/null +++ b/src/tests/modules/yubikey/yubikey_xlat.attrs @@ -0,0 +1,11 @@ +# +# Input packet +# +Packet-Type = Access-Request +User-Name = "bob" +User-Password = "hello" + +# +# Expected answer +# +Response-Packet-Type == Access-Accept diff --git a/src/tests/modules/yubikey/yubikey_xlat.unlang b/src/tests/modules/yubikey/yubikey_xlat.unlang new file mode 100644 index 0000000..bc17642 --- /dev/null +++ b/src/tests/modules/yubikey/yubikey_xlat.unlang @@ -0,0 +1,42 @@ +update { + &Tmp-String-0 := 'vvrbuctetdhc' + &Tmp-String-1 := "%{modhextohex:%{Tmp-String-0}}" +} + +if (&Tmp-String-1 != 'ffc1e0d3d260') { + test_fail +} + +# Invalid modhex string - not even length +update { + &Tmp-String-0 := 'vvrbuctetdh' + &Tmp-String-1 := "%{modhextohex:%{Tmp-String-0}}" +} + +if (ok) { + test_fail +} + +if (&Tmp-String-1 != "") { + test_fail +} + +if (&Module-Failure-Message != "Modhex string invalid") { + test_fail +} + +# Invalid modhex string - invalid characters +update { + &Tmp-String-0 := 'vxrbmctetdhc' + &Tmp-String-1 := "%{modhextohex:%{Tmp-String-0}}" +} + +if (ok) { + test_fail +} + +if (&Tmp-String-1 != "") { + test_fail +} + +test_pass diff --git a/src/tests/peap-gtc.conf b/src/tests/peap-gtc.conf new file mode 100644 index 0000000..bc8c74b --- /dev/null +++ b/src/tests/peap-gtc.conf @@ -0,0 +1,13 @@ +# +# ./eapol_test -c peap-gtc.conf -s testing123 +# +network={ + ssid="example" + key_mgmt=WPA-EAP + eap=PEAP + identity="bob" + anonymous_identity="anonymous" + password="bob" + phase1="" + phase2="auth=GTC" +} diff --git a/src/tests/unit/vendor.txt b/src/tests/unit/vendor.txt index 1325f49..088bd1b 100644 --- a/src/tests/unit/vendor.txt +++ b/src/tests/unit/vendor.txt @@ -46,3 +46,9 @@ original null encode ERX-LI-Action = off decode - data ERX-LI-Action = off + +encode Aruba-MPSK-Lookup-Info = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +data 1a c8 00 00 39 e7 42 c2 c5 6f 16 e5 de 2d 2a 2d d3 0e ac 92 12 c5 97 af 8e 08 f0 92 b4 45 4d 24 5d 73 16 a8 5a cd 78 0a f2 5e 7f e5 e1 fe 95 79 ee 2e 5b 0e ac bf fd 8c 15 da 9c 59 1d 53 5b 76 49 e9 71 4d d7 00 1c 04 65 51 cb 35 66 81 36 0d 25 ab 23 3b 67 5a 30 f8 0d 66 2b bf 97 f5 18 03 34 79 7a 22 11 c1 02 78 94 b0 26 62 13 4a c1 9c 77 6f b8 7c 29 ee 8b 61 14 de 90 b6 94 3f d0 01 00 57 6d 48 2a 59 f3 d4 57 d2 04 af 4e 64 0b 11 31 9e 63 49 f3 fa 61 4d c9 38 88 d1 89 3f 2a 10 d3 8f a0 5d 46 5f 0a b1 2f 9a 70 fa 35 79 c7 a6 68 69 28 98 49 d5 7a 29 9d dc 3d 2f 43 52 f5 12 b3 bf 61 80 2e 7a 3a 0c + +decode - +data Aruba-MPSK-Lookup-Info = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |