From 697398ccb1c44eb9690abb5a795374e1173306ca Mon Sep 17 00:00:00 2001 From: aland Date: Tue, 3 Apr 2007 13:25:58 +0000 Subject: [PATCH] Massive change to the server core to remove horrid code in request*.c, and move to an event-based system. Much better, and somewhat tested --- raddb/proxy.conf | 519 ++++++---- src/include/event.h | 50 + src/include/radiusd.h | 106 +- src/include/realms.h | 101 ++ src/include/request_list.h | 28 - src/lib/Makefile | 2 +- src/lib/event.c | 559 +++++++++++ src/main/Makefile.in | 19 +- src/main/acct.c | 8 +- src/main/auth.c | 7 +- src/main/event.c | 1841 +++++++++++++++++++++++++++++++++++ src/main/files.c | 368 ------- src/main/listen.c | 636 +++--------- src/main/mainconfig.c | 264 +---- src/main/modcall.c | 2 +- src/main/modules.c | 15 +- src/main/proxy.c | 565 ----------- src/main/radiusd.c | 68 +- src/main/realms.c | 1129 +++++++++++++++++++++ src/main/request_list.c | 703 ------------- src/main/request_process.c | 571 ----------- src/main/threads.c | 39 +- src/main/util.c | 8 +- src/modules/rlm_detail/rlm_detail.c | 29 +- src/modules/rlm_eap/eap.c | 7 +- src/modules/rlm_eap/rlm_eap.c | 4 +- src/modules/rlm_pap/rlm_pap.c | 14 +- src/modules/rlm_realm/rlm_realm.c | 63 +- 28 files changed, 4318 insertions(+), 3407 deletions(-) create mode 100644 src/include/event.h create mode 100644 src/include/realms.h delete mode 100644 src/include/request_list.h create mode 100644 src/lib/event.c create mode 100644 src/main/event.c delete mode 100644 src/main/proxy.c create mode 100644 src/main/realms.c delete mode 100644 src/main/request_list.c delete mode 100755 src/main/request_process.c diff --git a/raddb/proxy.conf b/raddb/proxy.conf index 433178d..f42aea0 100644 --- a/raddb/proxy.conf +++ b/raddb/proxy.conf @@ -12,207 +12,390 @@ # to which it sends proxy requests. # proxy server { -# -# The time (in seconds) to wait for a response from the proxy, before -# re-sending the proxied request. -# -# If this time is set too high, then the NAS may re-send the request, -# or it may give up entirely, and reject the user. -# -# If it is set too low, then the RADIUS server which receives the proxy -# request will get kicked unnecessarily. -# - retry_delay = 5 + # + # Note that as of 2.0, the "synchronous", "retry_delay", + # "retry_count", and "dead_time" have all been deprecated. + # For backwards compatibility, they are are still accepted + # by the server, but they ONLY apply to the old-style realm + # configuration. i.e. realms with "authhost" and/or "accthost" + # entries. + # + # i.e. "retry_delay" and "retry_count" have been replaced + # with per-home-server "pings". See the "home_server" example + # below for details. + # + # i.e. "dead_time" has been replaced with a per-home-server + # "revive_interval". We strongly recommend that this not + # be used, however. The new "ping" method is much better. + + # Note that while we call these messages "pings" they are NOT + # the same as the ICMP packets sent by the "ping" command. + # These messages are normal RADIUS packets, sent to a home + # server to determine if it is alive. + + # + # In 2.0, the server is always "synchronous", and setting + # "synchronous = no" is impossible. This simplifies the + # server and increases the stability of the network. + # + # If you need to set "synchronous = no", please send a + # message to the list + # explaining why this feature is vital for your network. + + # + # If a realm exists, but there are no live home servers for + # it, we can fall back to using the "DEFAULT" realm. This is + # most useful for accounting, where the server can proxy + # accounting requests to home servers, but if they're down, + # use a DEFAULT realm that is LOCAL (i.e. accthost = LOCAL), + # and then store the packets in the "detail" file. That data + # can be later proxied to the home servers by radrelay, when + # those home servers come back up again. + + # Setting this to "yes" may have issues for authentication. + # i.e. If you are proxying for two different ISP's, and then + # act as a general dial-up for Gric. If one of the first two + # ISP's has their RADIUS server go down, you do NOT want to + # proxy those requests to GRIC. Instead, you probably want + # to just drop the requests on the floor. In that case, set + # this value to 'no'. + # + # allowed values: {yes, no} + # + default_fallback = no + +} +####################################################################### # -# The number of retries to send before giving up, and sending a reject -# message to the NAS. +# Configuration for the proxy realms. # - retry_count = 3 - +# As of 2.0. the old-style "realms" file is deprecated, and is not +# used by FreeRADIUS. # -# If the home server does not respond to any of the multiple retries, -# then FreeRADIUS will stop sending it proxy requests, and mark it 'dead'. +# As of 2.0, the "realm" configuration has changed. Instead of +# specifying "authhost" and "accthost" in a realm section, the home +# servers are specified seperately in a "home_server" section. For +# backwards compatibility, you can still use the "authhost" and +# "accthost" directives. If you only have one home server for a +# realm, it is easier to use the old-style configuration. # -# If there are multiple entries configured for this realm, then the -# server will fail-over to the next one listed. If no more are listed, -# then no requests will be proxied to that realm. +# However, if you have multiple servers for a realm, we STRONGLY +# suggest moving to the new-style configuration. # # -# After a configurable 'dead_time', in seconds, FreeRADIUS will -# speculatively mark the home server active, and start sending requests -# to it again. +# Load-balancing and failover between home servers is handled via +# a "server_pool" section. # -# If this dead time is set too low, then you will lose requests, -# as FreeRADIUS will quickly switch back to the home server, even if -# it isn't up again. +# Finally, The "realm" section defines the realm, some options, and +# indicates which server pool should be used for the realm. # -# If this dead time is set too high, then FreeRADIUS may take too long -# to switch back to the primary home server. +# This change means that simple configurations now require multiple +# ssections to define a realm. However, complex configurations +# are much simpler than before, as multiple realms can share the same +# server pool. # -# Realistic values for this number are in the range of minutes to hours. -# (60 to 3600) +# That is, realms point to server pools, and server pools point to +# home servers. Multiple realms can point to one server pool. One +# server pool can point to multiple home servers. Each home server +# can appear in one or more pools. # - dead_time = 120 -# An ldflag attribute for all realms to be included in a round-robin -# setup must be specified, and that ldflag must be the same for all -# realms of the same name. -# Currently (0 or fail_over) and (1 or round_robin) are the -# supported values for ldflag. Fail over is the default setup. -# -# DO NOT INCLUDE LOCAL AUTH/ACCT HOST REALMS IN A ROUND-ROBIN QUEUE. +###################################################################### +# +# This section defines a "Home Server" which is another RADIUS +# server that gets sent proxied requests. In earlier versions +# of FreeRADIUS, home servers were defined in "realm" sections, +# which was awkward. In 2.0, they have been made independent +# from realms, which is better for a number of reasons. +# +home_server localhost { + # + # Home servers can be sent Access-Request packets + # or Accounting-Request packets. + # + # Allowed values are: + # auth - send Access-Request packets + # acct - send Accounting-Request packets + type = auth + # + # Configure ONE OF the following three entries: + # + # IPv4 address + # + ipaddr = 127.0.0.1 -# -# If all exact matching realms did not respond, we can try the -# DEFAULT realm, too. This is what the server normally does. -# -# This behaviour may be undesired for some cases. e.g. You are proxying -# for two different ISP's, and then act as a general dial-up for Gric. -# If one of the first two ISP's has their RADIUS server go down, you do -# NOT want to proxy those requests to GRIC. Instead, you probably want -# to just drop the requests on the floor. In that case, set this value -# to 'no'. -# -# allowed values: {yes, no} -# - default_fallback = yes + # OR IPv6 address + # ipv6addr = ::1 + # OR hostname, which will do address detection automatically + # + # Note that we do NOT recommend using hostnames, because + # it means that the server has to do a DNS lookup to + # determine the IP address of the home server. If the + # DNS server is slow or unresponsible, it means that + # FreeRADIUS will NOT be able to determine the IP + # address, and will therefore NOT start. + # + # hostname = localhost + + # + # The port to which packets are sent. + # + # Usually 1812 for type "auth", and 1813 for type "acct". + # Older servers may use 1645 and 1646. + # + port = 1812 + + # + # The shared secret use to "encrypt" and "sign" packets between + # FreeRADIUS and the home server. + # + # The secret can be any string, up to 8k characters in length. + # + # Control codes can be entered vi octal encoding, + # e.g. "\101\102" == "AB" + # Quotation marks can be entered by escaping them, + # e.g. "foo\"bar" + # Spaces or other "special" characters can be entered + # by putting quotes around the string. + # e.g. "foo bar" + # "foo;bar" + # + secret = testing123 + + ############################################################ + # + # The rest of the configuration items listed here are optional, + # and do not have to appear in every home server definition. + # + ############################################################ + + # + # If the home server doesn't respond to the request within + # this time, this server will consider the request dead, and + # respond to the NAS with an Access-Reject. + # + # Useful range of values: 5 to 60 + response_window = 20 + + # + # If the home server does not respond to ANY packets for + # a certain time, consider it dead. This time period is + # called the "zombie" period, because the server is neither + # alive nor dead. + # + # Useful range of values: 20 to 120 + zombie_period = 40 + + ############################################################ + # + # As of 2.0, FreeRADIUS supports RADIUS layer "pings". These + # are used by a proxy server to see if a home server is alive. + # + # Pings are sent ONLY if the proxying server believes that + # the home server is dead. Pings are NOT sent if the proxying + # server believes that the home server is alive. Pings are + # NOT sent if the proxying server is not proxying packets. + # + # If the home server responds to the "pings", then it is + # marked "alive" again, and is returned to use. + # + ############################################################ + + # + # Some home servers do not support RADIUS layer "pings" via + # the Status-Server packet. Others may not have a "test" + # user configured that can be used to query the server, to + # see if it is alive. For those servers, we have NO WAY + # of knowing when it becomes alive again. Therefore, after + # the server has been marked "dead", we wait a period of + # time, and mark it "alive" again, in the hope that it has + # come back to life. + # + # If it has NOT come back to life, then FreeRADIUS will wait + # for "zombie_period" before marking it dead again. During + # the "zombie_period", ALL AUTHENTICATIONS WILL FAIL, because + # the home server is still dead. There is NOTHING that can + # be done about this, other than to enable "pings", as + # documented below. + # + # e.g. if "zombie_period" is 40 seconds, and "revive_interval" + # is 300 seconds, the for 40 seconds out of every 340, or about + # 10% of the time, all authentications will fail. + # + # If the "zombie_period" and "revive_interval" configurations + # are set smaller, than it is possible for up to 50% of + # authentications to fail. + # + # As a result, we recommend enabling Status-Server "pings", and + # we do NOT recommend using "revive_interval". + # + # If the "ping_check" entry below is not "none", then the + # "revive_interval" entry can be deleted, as it will not be + # used. + # + # Useful range of values: 60 to 3600 + revive_interval = 120 + + # + # The proxying server (i.e. this one) can do periodic "ping" + # checks to see if a dead home server has come back alive. + # + # If set to "none", then the ping configuration items listed + # below are not used, and the "revive_interval" time is used + # instead. + # + # If set to "status-server", the Status-Server packets are + # sent. Many RADIUS servers support Status-Server. If a + # server does not support it, please contact the server + # vendor and request that they add it. + # + # If set to "request", then Access-Request, or Accounting-Request + # packets are sent, depending on the "type" entry above (auth/acct). + # + # Allowed values: none, status-server, request + ping_check = status-server + + # + # If the home server does not support Status-Server "pings", + # then the server can still send Access-Request or + # Accounting-Request packets, with a pre-defined user name. + # + # This practice is NOT recommended, as it may potentially let + # users gain network access by using these "test" accounts! + # + # If it is used, we recommend that the home server ALWAYS + # respond to Access-Request "pings" with Access-Reject. The + # ping check just needs an answer, it does not need an + # Access-Accept. + # + # For Accounting-Request "pings", only the username needs to + # be set. + # + # username = "test_user_please_reject_me" + # password = "this is really secret" + + # + # Configure the interval between sending ping packets. + # + # Setting it too low increases the probability of spurious + # fail-over and fallback attempts. + # + # Useful range of values: 6 to 120 + ping_interval = 30 + + # + # Configure the number of pings in a row that the home + # server needs to respond to before it is marked alive. + # + # If you want to mark a home server as alive after a short + # time period of being responsive, it is best to use a small + # "ping_interval", and a large value for "num_pings_to_alive". + # Using a long "ping_interval" and a small number for + # "num_pings_to_alive" increases the probability of spurious + # fail-over and fallback attempts. + # + # Useful range of values: 3 to 10 + num_pings_to_alive = 3 } -####################################################################### -# -# Configuration for the proxy realms. -# -# The information given here is used in conjunction with the 'realms' -# file. This format is preferred, as it is more flexible. The realms -# listed here take priority over those listed in the 'realms' file. -# A standard realm entry. A request from "user@company.com" will be -# sent to radius.company.com as "user", unless the 'nostrip' -# configuration item is specified. If the 'nostrip' configuration -# item is specified, then the request will be proxied as -# "user@company.com" -# -#realm company.com { -# type = radius -# authhost = radius.company.com:1600 -# accthost = radius.company.com:1601 -# secret = testing123 -#} +###################################################################### +# +# This section defines a pool of home servers that is used +# for fail-over and load-balancing. In earlier versions of +# FreeRADIUS, fail-over and load-balancing were defined per-realm. +# As a result, if a server had 5 home servers, each of which served +# the same 10 realms, you would need 50 "realm" entries. +# +# In version 2.0, you would need 5 "home_server" sections, +# 10 'realm" sections, and one "server_pool" section to tie the +# two together. +# +server_pool my_auth_failover { + # + # The type of this pool is either "fail-over" or "load-balance". + # + # With "fail-over", the request is sent to the first live + # home server in the list. + # + # With "load-balance", the request is load-balanced (randomly) + # between the live home servers. This is equivalent to the + # old per-realm configuration "round_robin". + # + type = fail-over -# A realm entry with an optional fail-over realm. A request from -# "user@isp2.com" will be sent to radius.isp2.com as "user@isp2.com", -# because the 'nostrip' directive is specified for this realm. -# -#realm isp2.com { -# type = radius -# authhost = radius.isp2.com:1645 -# accthost = radius.isp2.com:1646 -# secret = TheirKey -# nostrip -#} -# -# The fail-over realm for isp2.com -# -#realm isp2.com { -# type = radius -# authhost = radius2.isp2.com:1645 -# accthost = radius2.isp2.com:1646 -# secret = TheirKey2 -# nostrip -#} + # + # Next, a list of one or more home servers. The names + # of the home servers are NOT the hostnames, but the names + # of the sections. (e.g. home_server foo {...} has name "foo". + # + home_server = localhost + + # Additional home servers can be listed. + # There is NO LIMIT to the number of home servers that can + # be listed, though using more than 10 or so will become + # difficult to manage. + # + # home_server = foo.example.com + # home_server = bar.example.com + # home_server = baz.example.com + # home_server = ... +} +###################################################################### # -# 1st node serv.com...set up for round-robin. # -# The load balancing 'ldflag' attribute can be used to perform -# load balancing. Allowed values are 'fail_over' and 'round_robin'. +# This section defines a new-style "realm". Note the in version 2.0, +# there are many fewer configuration items than in 1.x for a realm. # -# If there is no ldflag attribute, or it is set to 'fail_over', then -# the realms are treated as "fail-over". That is, the first matching -# realm is used, unless it is down, in which case the realm "fails -# over" to the second matching realm. The process continues until an -# active matching realm is found, OR the DEFAULT realm is returned. +# Automatic proxying is done via the "realms" module (see "man +# rlm_realm"). To manually proxy the request put this entry in the +# "users" file: + # -# If the ldflag attribute is set to 'round_robin', then all active -# realms of the same name are put into a pool internally in the -# server, and the proxied requests are evenly divided among the -# realms in the pool. For this to work, all realms of the same name -# MUST have the same value of their 'ldflag' attributes. Mixing up -# different types of load balancing schemes for the same realm will -# cause problems. # -# The round_robin load balancing method is a probabilistic method -# which evenly scatters the requests among the home servers. +#DEFAULT Proxy-To-Realm := "realm_name" # -# Note that you CANNOT include local auth/acct host realms in a -# round-robin queue. Having a server load balance requests to itself -# doesn't make any sense, as it only doubles the amount of work -# which is needed to be done. # -#realm serv.com { -# type = radius -# authhost = radius.serv.com:1645 -# accthost = radius.serv.com:1646 -# secret = TheirKey -# ldflag = round_robin -# nostrip -#} +realm example.com { + auth_pool = my_auth_failover +# acct_pool = acct -# -# Another node for serv.com -# -#realm serv.com { -# type = radius -# authhost = radius2.serv.com:1645 -# accthost = radius2.serv.com:1646 -# secret = TheirKey2 -# ldflag = round_robin -# nostrip -#} + # + # Normally, when an incoming User-Name is matched against the + # realm, the realm name is "stripped" off, and the "stripped" + # user name is used to perform matches. + # + # e.g. User-Name = "bob@example.com" will result in two new + # attributes being created by the "realms" module: + # + # Stripped-User-Name = "bob" + # Realm = "example.com" + # + # The Stripped-User-Name is then used as a key in the "users" + # file, for example. + # + # If you do not want this to happen, uncomment "nostrip" below. + # + # nostrip -# -# A third round-robin node realm for serv.com -# -#realm serv.com { -# type = radius -# authhost = radius3.serv.com:1645 -# accthost = radius3.serv.com:1646 -# secret = TheirKey2 -# ldflag = round_robin -# nostrip -#} -# -# + # There are no more configuration entries for a realm. +} -# -# This is a local realm. The requests are NOT proxied, -# but instead are authenticated by the RADIUS server itself. -# -# You don't need a secret if BOTH 'authhost' and 'accthost' are -# set to LOCAL. -# -#realm bla.com { -# type = radius -# authhost = LOCAL -# accthost = LOCAL -#} # # This is a sample entry for iPass. +# Note that you have to define "ipass_auth_pool" and +# "ipass_acct_pool", along with home_servers for them, too. # #realm IPASS { -# type = radius -# authhost = ipass.server.hostname:11812 -# accthost = ipass.server.hostname:11813 -# - # The shared secret here must be the same - # value as the secret of the NetServer found in the - # /usr/ipass/raddb/clients file of your NetServer software. -# secret = mysecret # nostrip +# +# auth_pool = ipass_auth_pool +# acct_pool = ipass_acct_pool #} # @@ -227,9 +410,8 @@ proxy server { # DEFAULT EAP-Type == PEAP, Proxy-To-Realm := LOCAL # realm LOCAL { - type = radius - authhost = LOCAL - accthost = LOCAL + # If we do not specify a server pool, the realm is LOCAL, and + # requests are not proxied to it. } # @@ -252,3 +434,4 @@ realm LOCAL { # accthost = radius.company.com:1601 # secret = testing123 #} + diff --git a/src/include/event.h b/src/include/event.h new file mode 100644 index 0000000..c44b27d --- /dev/null +++ b/src/include/event.h @@ -0,0 +1,50 @@ +#ifndef LRAD_EVENT_H +#define LRAD_EVENT_H + +/* + * event.h Simple event queue + * + * Version: $Id$ + * + * 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 + * + * Copyright 2007 The FreeRADIUS server project + * Copyright 2007 Alan DeKok + */ + +#include +RCSIDH(event_h, "$Id$") + +typedef struct lrad_event_list_t lrad_event_list_t; +typedef void (*lrad_event_callback_t)(void *); + +lrad_event_list_t *lrad_event_list_create(void); +void lrad_event_list_free(lrad_event_list_t *el); + +int lrad_event_list_num_elements(lrad_event_list_t *el); + +int lrad_event_insert(lrad_event_list_t *el, lrad_event_callback_t callback, + void *ctx, struct timeval *when); +int lrad_event_delete(lrad_event_list_t *el, void *ctx); + +int lrad_event_callback(lrad_event_list_t *el, void *ctx, + lrad_event_callback_t *pcallback); +int lrad_event_when(lrad_event_list_t *el, void *ctx, struct timeval *when); + +int lrad_event_run(lrad_event_list_t *el, struct timeval *when); + +int lrad_event_now(lrad_event_list_t *el, struct timeval *when); + +#endif /* LRAD_HASH_H */ diff --git a/src/include/radiusd.h b/src/include/radiusd.h index 7e7f323..cd6d199 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -16,6 +16,7 @@ RCSIDH(radiusd_h, "$Id$") #include #include #include +#include #ifdef HAVE_UNISTD_H @@ -31,9 +32,6 @@ typedef pid_t child_pid_t; #define child_kill kill #endif -#ifdef HAVE_NETINET_IN_H -#endif - #define NO_SUCH_CHILD_PID (child_pid_t) (0) #ifndef NDEBUG @@ -50,11 +48,6 @@ typedef struct request_data_t request_data_t; */ typedef struct rad_listen_t rad_listen_t; -/* - * For request lists. - */ -typedef struct request_list_t request_list_t; - #define REQUEST_DATA_REGEX (0xadbeef00) #define REQUEST_MAX_REGEX (8) @@ -79,37 +72,47 @@ typedef struct auth_req { rad_listen_t *listener; rad_listen_t *proxy_listener; - /* - * We could almost keep a const char here instead of a - * _copy_ of the secret... but what if the RADCLIENT - * structure is freed because it was taken out of the - * config file and SIGHUPed? - */ - char proxysecret[32]; - int proxy_try_count; - int proxy_outstanding; - time_t proxy_start_time; - int simul_max; int simul_count; int simul_mpp; /* WEIRD: 1 is false, 2 is true */ - int finished; int options; /* miscellanous options */ const char *module; /* for debugging unresponsive children */ const char *component; /* ditto */ - void *container; + + struct timeval received; + struct timeval when; /* to wake up */ + int delay; + + int master_state; + int child_state; + + struct timeval next_when; + void *next_callback; + + int in_request_hash; + int in_proxy_hash; + + home_server *home_server; + + struct timeval proxy_when; + + int num_proxied_requests; + int num_proxied_responses; + } REQUEST; #define RAD_REQUEST_OPTION_NONE (0) -#define RAD_REQUEST_OPTION_LOGGED_CHILD (1 << 0) -#define RAD_REQUEST_OPTION_DELAYED_REJECT (1 << 1) -#define RAD_REQUEST_OPTION_DONT_CACHE (1 << 2) -#define RAD_REQUEST_OPTION_FAKE_REQUEST (1 << 3) -#define RAD_REQUEST_OPTION_REJECTED (1 << 4) -#define RAD_REQUEST_OPTION_PROXIED (1 << 5) -#define RAD_REQUEST_OPTION_STOP_NOW (1 << 6) -#define RAD_REQUEST_OPTION_REPROCESS (1 << 7) + +#define REQUEST_ACTIVE (1) +#define REQUEST_STOP_PROCESSING (2) + +#define REQUEST_QUEUED (1) +#define REQUEST_RUNNING (2) +#define REQUEST_PROXIED (3) +#define REQUEST_REJECT_DELAY (4) +#define REQUEST_CLEANUP_DELAY (5) +#define REQUEST_DONE (6) /* * Function handler for requests. @@ -130,27 +133,6 @@ typedef struct radclient { typedef struct radclient_list RADCLIENT_LIST; -typedef struct _realm { - char realm[64]; - char server[64]; - char acct_server[64]; - lrad_ipaddr_t ipaddr; /* authentication */ - lrad_ipaddr_t acct_ipaddr; - char secret[32]; - time_t last_reply; /* last time we saw a packet */ - int auth_port; - int acct_port; - int striprealm; - int trusted; /* old */ - int notrealm; - int active; /* is it dead? */ - time_t wakeup; /* when we should try it again */ - int acct_active; - time_t acct_wakeup; - int ldflag; - struct _realm *next; -} REALM; - typedef struct pair_list { char *name; VALUE_PAIR *check; @@ -178,8 +160,9 @@ typedef enum RAD_LISTEN_TYPE { typedef int (*rad_listen_recv_t)(rad_listen_t *, RAD_REQUEST_FUNP *, REQUEST **); typedef int (*rad_listen_send_t)(rad_listen_t *, REQUEST *); -typedef int (*rad_listen_update_t)(rad_listen_t *, time_t); typedef int (*rad_listen_print_t)(rad_listen_t *, char *, size_t); +typedef int (*rad_listen_encode_t)(rad_listen_t *, REQUEST *); +typedef int (*rad_listen_decode_t)(rad_listen_t *, REQUEST *); struct rad_listen_t { struct rad_listen_t *next; /* should be rbtree stuff */ @@ -190,11 +173,11 @@ struct rad_listen_t { RAD_LISTEN_TYPE type; int fd; const char *identity; - request_list_t *rl; rad_listen_recv_t recv; rad_listen_send_t send; - rad_listen_update_t update; + rad_listen_encode_t encode; + rad_listen_decode_t decode; rad_listen_print_t print; void *data; @@ -250,7 +233,6 @@ typedef struct main_config_t { radlog_dest_t radlog_dest; CONF_SECTION *config; RADCLIENT_LIST *clients; - REALM *realms; const char *radiusd_conf; } MAIN_CONFIG_T; @@ -380,14 +362,9 @@ RADCLIENT *client_find_old(const lrad_ipaddr_t *ipaddr); const char *client_name_old(const lrad_ipaddr_t *ipaddr); /* files.c */ -REALM *realm_find(const char *, int); -REALM *realm_findbyaddr(uint32_t ipno, int port); -void realm_free(REALM *cl); -void realm_disable(REQUEST *); int pairlist_read(const char *file, PAIR_LIST **list, int complain); void pairlist_free(PAIR_LIST **); int read_config_files(void); -int read_realms_file(const char *file); /* version.c */ void version(void); @@ -433,7 +410,6 @@ void paircompare_unregister(int attr, RAD_COMPARE_FUNC func); int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check, VALUE_PAIR **reply); int simplepaircmp(REQUEST *, VALUE_PAIR *, VALUE_PAIR *); -void pair_builtincompare_init(void); void pairxlatmove(REQUEST *, VALUE_PAIR **to, VALUE_PAIR **from); /* xlat.c */ @@ -471,4 +447,14 @@ void listen_free(rad_listen_t **head); int listen_init(const char *filename, rad_listen_t **head); rad_listen_t *proxy_new_listener(void); +/* event.c */ +int radius_event_init(int spawn_flag); +void radius_event_free(void); +int radius_event_process(struct timeval **pptv); +void radius_handle_request(REQUEST *request, RAD_REQUEST_FUNP fun); +int received_request(rad_listen_t *listener, + RADIUS_PACKET *packet, REQUEST **prequest, + const RADCLIENT *client); +REQUEST *received_proxy_response(RADIUS_PACKET *packet); + #endif /*RADIUSD_H*/ diff --git a/src/include/realms.h b/src/include/realms.h new file mode 100644 index 0000000..12f57ce --- /dev/null +++ b/src/include/realms.h @@ -0,0 +1,101 @@ +#ifndef REALMS_H +#define REALMS_H + +/* + * realms.h Structures, prototypes and global variables + * for realms + * + * Version: $Id$ + * + */ + +#include +RCSIDH(realms_h, "$Id$") + +#define HOME_TYPE_AUTH (1) +#define HOME_TYPE_ACCT (2) + +#define HOME_PING_CHECK_NONE (0) +#define HOME_PING_CHECK_STATUS_SERVER (1) +#define HOME_PING_CHECK_REQUEST (2) + +#define HOME_STATE_ALIVE (0) +#define HOME_STATE_ZOMBIE (1) +#define HOME_STATE_IS_DEAD (2) + +typedef struct home_server { + const char *name; + + const char *hostname; + + lrad_ipaddr_t ipaddr; + + + int port; + int type; /* auth/acct */ + + /* + * Maybe also have list of source IP/ports, && socket? + */ + + const char *secret; + + struct timeval when; + + int response_window; + int max_outstanding; /* don't overload it */ + int currently_outstanding; + + struct timeval zombie_period_start; + int zombie_period; /* unresponsive for T, mark it dead */ + + int state; + + int ping_check; + const char *ping_user_name; + const char *ping_user_password; + + int ping_interval; + int num_pings_to_alive; + int num_received_pings; + + int revive_interval; /* if it doesn't support pings */ +} home_server; + + +typedef enum home_pool_type_t { + HOME_POOL_INVALID = 0, + HOME_POOL_LOAD_BALANCE, + HOME_POOL_FAIL_OVER +} home_pool_type_t; + + +typedef struct home_pool_t { + const char *name; + home_pool_type_t type; + + int server_type; + + int num_home_servers; + home_server *servers[1]; +} home_pool_t; + + +typedef struct _realm { + const char *name; + + int striprealm; + + home_pool_t *auth_pool; + home_pool_t *acct_pool; +} REALM; + +int realms_init(const char *filename); +void realms_free(void); +int realm_add(const char *filename, CONF_SECTION *cs); +REALM *realm_find(const char *name); + +home_server *home_server_ldb(REALM *realm, int code); +home_server *home_server_find(lrad_ipaddr_t *ipaddr, int port); + +#endif /* REALMS_H */ diff --git a/src/include/request_list.h b/src/include/request_list.h deleted file mode 100644 index 3ab2d73..0000000 --- a/src/include/request_list.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef _REQUEST_LIST_H -#define _REQUEST_LIST_H -/* - * request_list.h Hide the handling of the REQUEST list from - * the main server. - * - * Version: $Id$ - * - */ - -#include -RCSIDH(request_list_h, "$Id$") - -extern request_list_t *rl_init(void); -extern void rl_deinit(request_list_t *); -extern void rl_yank(request_list_t *, REQUEST *); -extern void rl_delete(request_list_t *, REQUEST *); -extern int rl_add(request_list_t *, REQUEST *); -extern REQUEST *rl_find(request_list_t *, RADIUS_PACKET *); - -extern int rl_init_proxy(void); -extern int rl_add_proxy(REQUEST *request); -extern REQUEST *rl_find_proxy(RADIUS_PACKET *packet); -extern REQUEST *rl_next(request_list_t *, REQUEST *); -extern int rl_num_requests(request_list_t *); -extern int rl_clean_list(request_list_t *, time_t now); - -#endif /* _REQUEST_LIST_H */ diff --git a/src/lib/Makefile b/src/lib/Makefile index e975982..f0e14e0 100644 --- a/src/lib/Makefile +++ b/src/lib/Makefile @@ -9,7 +9,7 @@ include ../../Make.inc SRCS = dict.c filters.c hash.c hmac.c hmacsha1.c isaac.c log.c \ misc.c missing.c md4.c md5.c print.c radius.c rbtree.c \ sha1.c snprintf.c strlcat.c strlcpy.c token.c udpfromto.c \ - valuepair.c fifo.c packet.c + valuepair.c fifo.c packet.c event.c LT_OBJS = $(SRCS:.c=.lo) diff --git a/src/lib/event.c b/src/lib/event.c new file mode 100644 index 0000000..11d9784 --- /dev/null +++ b/src/lib/event.c @@ -0,0 +1,559 @@ +/* + * event.c Non-thread-safe event handling, specific to a RADIUS + * server. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2007 The FreeRADIUS server project + * Copyright 2007 Alan DeKok + */ + +#include +RCSID("$Id$") + +#include + +#include +#include + +#include +#include +#include + +typedef struct lrad_event_fd_t { + int fd; + int priority; + lrad_event_callback_t callback; + void *ctx; + struct lrad_event_fd_t *next; +} lrad_event_fd_t; + +struct lrad_event_list_t { + rbtree_t *times; + rbtree_t *contexts; + + lrad_event_fd_t *fds; + int maxfd; + fd_set readfds; + + int exit; + + struct timeval now; + int dispatch; +}; + +/* + * Internal structure for managing events. + */ +typedef struct lrad_event_t { + lrad_event_callback_t callback; + void *ctx; + struct timeval when; +} lrad_event_t; + + +static int lrad_event_list_time_cmp(const void *one, const void *two) +{ + const lrad_event_t *a = one; + const lrad_event_t *b = two; + + if (a->when.tv_sec < b->when.tv_sec) return -1; + if (a->when.tv_sec > b->when.tv_sec) return +1; + + if (a->when.tv_usec < b->when.tv_usec) return -1; + if (a->when.tv_usec > b->when.tv_usec) return +1; + + return 0; +} + +static int lrad_event_list_ctx_cmp(const void *one, const void *two) +{ + const lrad_event_t *a = one; + const lrad_event_t *b = two; + + if (a->ctx < b->ctx) return -1; + if (a->ctx > b->ctx) return +1; + + return 0; +} + + +void lrad_event_list_free(lrad_event_list_t *el) +{ + if (!el) return; + + rbtree_free(el->times); + rbtree_free(el->contexts); + free(el); +} + + +lrad_event_list_t *lrad_event_list_create(void) +{ + lrad_event_list_t *el; + + el = malloc(sizeof(*el)); + if (!el) return NULL; + memset(el, 0, sizeof(*el)); + + el->times = rbtree_create(lrad_event_list_time_cmp, + free, 0); + if (!el->times) { + lrad_event_list_free(el); + return NULL; + } + + el->contexts = rbtree_create(lrad_event_list_ctx_cmp, + NULL, 0); + if (!el->contexts) { + lrad_event_list_free(el); + return NULL; + } + + return el; +} + +int lrad_event_list_num_elements(lrad_event_list_t *el) +{ + if (!el) return 0; + + return rbtree_num_elements(el->times); +} + + +int lrad_event_delete(lrad_event_list_t *el, void *ctx) +{ + lrad_event_t my_ev, *ev; + + if (!el || !ctx) return 0; + + my_ev.ctx = ctx; + ev = rbtree_finddata(el->contexts, &my_ev); + if (!ev) return 0; + + rbtree_deletebydata(el->contexts, ev); + rbtree_deletebydata(el->times, ev); + + return 1; +} + + +int lrad_event_insert(lrad_event_list_t *el, lrad_event_callback_t callback, + void *ctx, struct timeval *when) +{ + lrad_event_t *ev; + + if (!el || !callback | !when) return 0; + + lrad_event_delete(el, ctx); /* can only be 1 event per ctx */ + + ev = malloc(sizeof(*ev)); + if (!ev) return 0; + memset(ev, 0, sizeof(*ev)); + + ev->callback = callback; + ev->ctx = ctx; + ev->when = *when; + + if (!rbtree_insert(el->contexts, ev)) { + free(ev); + return 0; + } + + /* + * There's a tiny chance that two events will be + * scheduled at the same time. If this happens, we + * increase the usec counter by 1, in order to avoid the + * duplicate. If we can't insert it after 10 tries, die. + */ + if (!rbtree_insert(el->times, ev)) { + if (rbtree_finddata(el->times, ev)) { + int i; + + for (i = 0; i < 10; i++) { + ev->when.tv_usec++; + if (ev->when.tv_usec >= 1000000) { + ev->when.tv_usec = 0; + ev->when.tv_sec++; + } + + if (rbtree_finddata(el->times, ev)) { + continue; + } + + if (!rbtree_insert(el->times, ev)) { + break; + } + + return 1; + } + + } + rbtree_deletebydata(el->contexts, ev); + free(ev); + return 0; + } + + return 1; +} + +int lrad_event_callback(lrad_event_list_t *el, void *ctx, + lrad_event_callback_t *pcallback) +{ + lrad_event_t my_ev, *ev; + + if (!el || !ctx || !pcallback) return 0; + + my_ev.ctx = ctx; + ev = rbtree_finddata(el->contexts, &my_ev); + if (!ev) return 0; + + *pcallback = ev->callback; + return 1; +} + + +int lrad_event_when(lrad_event_list_t *el, void *ctx, struct timeval *when) +{ + lrad_event_t my_ev, *ev; + + if (!el || !ctx) return 0; + + my_ev.ctx = ctx; + ev = rbtree_finddata(el->contexts, &my_ev); + if (!ev) return 0; + + *when = ev->when; + return 1; +} + + +typedef struct lrad_event_walk_t { + lrad_event_t *ev; + struct timeval when; +} lrad_event_walk_t; + + +static int lrad_event_find_earliest(void *ctx, void *data) +{ + lrad_event_t *ev = data; + lrad_event_walk_t *w = ctx; + + if (ev->when.tv_sec > w->when.tv_sec) { + w->when = ev->when; + return 1; + } + + if ((ev->when.tv_sec == w->when.tv_sec) && + (ev->when.tv_usec > w->when.tv_usec)) { + w->when = ev->when; + return 1; + } + + w->ev = ev; + return 1; +} + + +int lrad_event_run(lrad_event_list_t *el, struct timeval *when) +{ + lrad_event_callback_t callback; + void *ctx; + lrad_event_walk_t w; + + if (!el) return 0; + + w.ev = NULL; + w.when = *when; + + if (rbtree_num_elements(el->times) == 0) { + when->tv_sec = 0; + when->tv_usec = 0; + return 0; + } + + rbtree_walk(el->times, InOrder, lrad_event_find_earliest, &w); + if (!w.ev) { + *when = w.when; + return 0; + } + + callback = w.ev->callback; + ctx = w.ev->ctx; + + /* + * Delete the event before calling it. + */ + rbtree_deletebydata(el->contexts, w.ev); + rbtree_deletebydata(el->times, w.ev); + + callback(ctx); + return 1; +} + + +static int lrad_event_insert_fd(lrad_event_list_t *el, int fd, int priority, + lrad_event_callback_t callback, void *ctx) +{ + lrad_event_fd_t **last, *ef; + + if (!fd || (fd < 0) || (priority < 0) || !callback || !ctx) return 0; + + ef = malloc(sizeof(*ef)); + if (!ef) return 0; + memset(ef, 0, sizeof(*ef)); + + ef->fd = fd; + ef->priority = priority; + ef->callback = callback; + ef->ctx = ctx; + + if (!el->fds) { + el->fds = ef; + el->maxfd = fd + 1; + FD_ZERO(&el->readfds); + FD_SET(fd, &el->readfds); + return 1; + } + + for (last = &(el->fds); + *last != NULL; + last = &((*last)->next)) { + if ((*last)->priority < priority) { + ef->next = *last; + *last = ef; + + if (fd >= el->maxfd) { + el->maxfd = fd + 1; + } + FD_SET(fd, &el->readfds); + + return 1; + } + } + + return 0; +} + + +static int lrad_event_delete_fd(lrad_event_list_t *el, int fd) +{ + lrad_event_fd_t **last; + + if (!el || (fd < 0)) return 0; + + for (last = &el->fds; + *last != NULL; + last = &((*last)->next)) { + if ((*last)->fd == fd) { + lrad_event_fd_t *ef = *last; + *last = (*last)->next; + free(ef); + return 1; + } + } + + return 0; +} + +int lrad_event_now(lrad_event_list_t *el, struct timeval *when) +{ + if (!el || !when || !el->dispatch) return 0; + + *when = el->now; + return 1; +} + +static void lrad_event_exit_loop_cb(void *data) +{ + lrad_event_list_t *el = data; + + el->exit = 1; +} + +static int lrad_event_exit_loop(lrad_event_list_t *el, struct timeval *when) +{ + if (!el || !when) return 0; + + return lrad_event_insert(el, lrad_event_exit_loop_cb, el, when); +} + + +static int lrad_event_dispatch(lrad_event_list_t *el) +{ + if (!el || !el->fds) return 0; + + el->dispatch = 1; + + while (!el->exit) { + int rcode; + fd_set readfds; + struct timeval when, *timeout; + lrad_event_fd_t *ef; + + gettimeofday(&el->now, NULL); + while (1) { + when = el->now; + + if (!lrad_event_run(el, &when)) { + break; + } + } + gettimeofday(&el->now, NULL); + + if (rbtree_num_elements(el->times) == 0) { + timeout = NULL; + + } else if ((el->now.tv_sec >= when.tv_sec) || + ((el->now.tv_sec == when.tv_sec) && + (el->now.tv_usec >= when.tv_usec))) { + timeout = &when; + when.tv_sec = 0; + when.tv_usec = 0; + + } else { + timeout = &when; + + when.tv_sec -= el->now.tv_sec; + if (when.tv_sec == 0) { + when.tv_usec -= el->now.tv_usec; + } else if (when.tv_usec > el->now.tv_usec) { + when.tv_usec -= el->now.tv_usec; + } else { + when.tv_sec--; + when.tv_usec += 1000000; + when.tv_usec -= el->now.tv_usec; + } + } + + readfds = el->readfds; + + rcode = select(el->maxfd, &readfds, NULL, NULL, timeout); + if (rcode == 0) { + continue; + } + + if (rcode < 0) { + el->dispatch = 0; + el->exit = 0; + return -1; + } + + for (ef = el->fds; ef != NULL; ef = ef->next) { + if (FD_ISSET(ef->fd, &readfds)) { + ef->callback(ef->ctx); + continue; /* starve lower priority FD's! */ + } + } + } + + el->exit = 0; + el->dispatch = 0; + + return 1; +} + + +#ifdef TESTING + +/* + * cc -g -I .. -c rbtree.c -o rbtree.o && cc -g -I .. -c isaac.c -o isaac.o && cc -DTESTING -I .. -c event.c -o event_mine.o && cc event_mine.o rbtree.o isaac.o -o event + * + * ./event + * + * And hit CTRL-S to stop the output, CTRL-Q to continue. + * It normally alternates printing the time and sleeping, + * but when you hit CTRL-S/CTRL-Q, you should see a number + * of events run right after each other. + * + * OR + * + * valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./event + */ + +static void print_time(void *ctx) +{ + struct timeval *when = ctx; + + printf("%d.%06d\n", when->tv_sec, when->tv_usec); + fflush(stdout); +} + +static lrad_randctx rand_pool; + +static uint32_t event_rand(void) +{ + uint32_t num; + + num = rand_pool.randrsl[rand_pool.randcnt++]; + if (rand_pool.randcnt == 256) { + lrad_isaac(&rand_pool); + rand_pool.randcnt = 0; + } + + return num; +} + + +#define MAX 100 +int main(int argc, char **argv) +{ + int i, rcode; + struct timeval array[MAX]; + struct timeval now, when; + lrad_event_list_t *el; + + el = lrad_event_list_create(); + if (!el) exit(1); + + memset(&rand_pool, 0, sizeof(rand_pool)); + rand_pool.randrsl[1] = time(NULL); + + lrad_randinit(&rand_pool, 1); + rand_pool.randcnt = 0; + + gettimeofday(&array[0], NULL); + for (i = 1; i < MAX; i++) { + array[i] = array[i - 1]; + + array[i].tv_usec += event_rand() & 0xffff; + if (array[i].tv_usec > 1000000) { + array[i].tv_usec -= 1000000; + array[i].tv_sec++; + } + lrad_event_insert(el, print_time, &array[i], &array[i]); + } + + while (lrad_event_list_num_elements(el)) { + gettimeofday(&now, NULL); + when = now; + if (!lrad_event_run(el, &when)) { + int delay = (when.tv_sec - now.tv_sec) * 1000000; + delay += when.tv_usec; + delay -= now.tv_usec; + + printf("\tsleep %d\n", delay); + fflush(stdout); + usleep(delay); + } + } + + lrad_event_list_free(el); + + return 0; +} +#endif diff --git a/src/main/Makefile.in b/src/main/Makefile.in index b28ad98..03b6e3d 100644 --- a/src/main/Makefile.in +++ b/src/main/Makefile.in @@ -5,10 +5,10 @@ include ../../Make.inc SERVER_SRCS = acct.c auth.c client.c conffile.c crypt.c exec.c files.c \ - listen.c log.c mainconfig.c modules.c modcall.c proxy.c \ - radiusd.c radius_snmp.c request_list.c request_process.c \ + listen.c log.c mainconfig.c modules.c modcall.c \ + radiusd.c radius_snmp.c \ session.c smux.c threads.c util.c valuepair.c version.c \ - xlat.c + xlat.c event.c realms.c SERVER_OBJS += $(SERVER_SRCS:.c=.lo) @@ -71,7 +71,7 @@ radiusd: $(SERVER_OBJS) $(MODULE_OBJS) ../lib/libradius.la $(MODULE_LIBS) $(LIBS) $(SNMP_LIBS) $(LCRYPT) \ $(PTHREADLIB) $(LIBLTDL) $(OPENSSL_LIBS) -radiusd.lo: radiusd.c ../include/request_list.h ../include/modules.h ../include/modcall.h ../include/modpriv.h +radiusd.lo: radiusd.c ../include/modules.h ../include/modcall.h ../include/modpriv.h $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c radiusd.c acct.lo: acct.c ../include/modules.h @@ -110,17 +110,14 @@ modcall.lo: modcall.c modules.lo: modules.c $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(VFLAGS) $(INCLTDL) -c modules.c -proxy.lo: proxy.c - $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c proxy.c - radius_snmp.lo: radius_snmp.c $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c radius_snmp.c -request_list.lo: request_list.c - $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c request_list.c +event.lo: event.c + $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c event.c -request_process.lo: request_process.c - $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c request_process.c +realms.lo: realms.c + $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c realms.c session.lo: session.c ../include/modules.h $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c session.c diff --git a/src/main/acct.c b/src/main/acct.c index 6dc9305..0059c6f 100644 --- a/src/main/acct.c +++ b/src/main/acct.c @@ -122,11 +122,9 @@ int rad_accounting(REQUEST *request) * Check whether Proxy-To-Realm is * a LOCAL realm. */ - realm = realm_find(vp->vp_strvalue, TRUE); - if (realm != NULL && - realm->acct_ipaddr.af == AF_INET && - realm->acct_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)) { - DEBUG("rad_accounting: Cancelling proxy to realm %s, as it is a LOCAL realm.", realm->realm); + realm = realm_find(vp->vp_strvalue); + if (realm && !realm->acct_pool) { + DEBUG("rad_accounting: Cancelling proxy to realm %s, as it is a LOCAL realm.", realm->name); pairdelete(&request->config_items, PW_PROXY_TO_REALM); } else { /* diff --git a/src/main/auth.c b/src/main/auth.c index ffa3a6b..94bed74 100644 --- a/src/main/auth.c +++ b/src/main/auth.c @@ -621,10 +621,9 @@ autz_redo: * Catch users who set Proxy-To-Realm to a LOCAL * realm (sigh). */ - realm = realm_find(tmp->vp_strvalue, 0); - rad_assert((realm == NULL) || (realm->ipaddr.af == AF_INET)); - if (realm && (realm->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE))) { - DEBUG2(" WARNING: You set Proxy-To-Realm = %s, but it is a LOCAL realm! Cancelling invalid proxy request.", realm->realm); + realm = realm_find(tmp->vp_strvalue); + if (realm && !realm->auth_pool) { + DEBUG2(" WARNING: You set Proxy-To-Realm = %s, but it is a LOCAL realm! Cancelling invalid proxy request.", realm->name); } else { /* * Don't authenticate, as the request is diff --git a/src/main/event.c b/src/main/event.c new file mode 100644 index 0000000..6d048e4 --- /dev/null +++ b/src/main/event.c @@ -0,0 +1,1841 @@ +/* + * event.c Server event handling + * + * Version: $Id$ + * + * 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 + * + * Copyright 2007 The FreeRADIUS server project + * Copyright 2007 Alan DeKok + */ + +#include +RCSID("$Id$") + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define USEC (1000000) + +/* + * Ridiculous amounts of local state. + */ +static lrad_event_list_t *el = NULL; +static lrad_packet_list_t *pl = NULL; +static int request_num_counter = 0; +static struct timeval now; +static time_t start_time; +static int have_children; + +#ifdef HAVE_PTHREAD_H +static pthread_mutex_t proxy_mutex; + +#define PTHREAD_MUTEX_LOCK if (have_children) pthread_mutex_lock +#define PTHREAD_MUTEX_UNLOCK if (have_children) pthread_mutex_unlock +#else +/* + * This is easier than ifdef's throughout the code. + */ +#define PTHREAD_MUTEX_LOCK(_x) +#define PTHREAD_MUTEX_UNLOCK(_x) +#endif + +static lrad_packet_list_t *proxy_list = NULL; + +/* + * We keep the proxy FD's here. The RADIUS Id's are marked + * "allocated" per Id, via a bit per proxy FD. + */ +static int proxy_fds[32]; +static rad_listen_t *proxy_listeners[32]; + + +static void _rad_panic(const char *file, unsigned int line, const char *msg) +{ + radlog(L_ERR, "]%s:%d] %s", file, line, msg); + _exit(1); +} + +#define rad_panic(x) _rad_panic(__FILE__, __LINE__, x) + + +/* + * FIXME FIXME: Do SNMP, but smarter... have an array[code].foo, + * so we increment counters by just using code as an offset. The + * array should be the union of the server && client SNMP + * variables, which should simplify it a lot. + */ + +static void tv_add(struct timeval *tv, int usec_delay) +{ + if (usec_delay > USEC) { + tv->tv_sec += usec_delay / USEC; + usec_delay %= USEC; + } + tv->tv_usec += usec_delay; + + if (tv->tv_usec > USEC) { + tv->tv_usec -= USEC; + tv->tv_sec++; + } +} + + +static void remove_from_request_hash(REQUEST *request) +{ +#ifdef WITH_SNMP + /* + * Update the SNMP statistics. + * + * Note that we do NOT do this in a child thread. + * Instead, we update the stats when a request is + * deleted, because only the main server thread calls + * this function... + */ + if (mainconfig.do_snmp) { + switch (request->reply->code) { + case PW_AUTHENTICATION_ACK: + rad_snmp.auth.total_responses++; + rad_snmp.auth.total_access_accepts++; + break; + + case PW_AUTHENTICATION_REJECT: + rad_snmp.auth.total_responses++; + rad_snmp.auth.total_access_rejects++; + break; + + case PW_ACCESS_CHALLENGE: + rad_snmp.auth.total_responses++; + rad_snmp.auth.total_access_challenges++; + break; + + case PW_ACCOUNTING_RESPONSE: + rad_snmp.acct.total_responses++; + break; + + default: + break; + } + } +#endif + + if (!request->in_request_hash) return; + + lrad_packet_list_yank(pl, request->packet); + request->in_request_hash = FALSE; +} + + +static REQUEST *lookup_in_proxy_hash(RADIUS_PACKET *reply) +{ + RADIUS_PACKET **proxy_p; + REQUEST *request; + + PTHREAD_MUTEX_LOCK(&proxy_mutex); + proxy_p = lrad_packet_list_find_byreply(proxy_list, reply); + + if (!proxy_p) { + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + return NULL; + } + + request = lrad_packet2myptr(REQUEST, proxy, proxy_p); + + if (!request) { + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + return NULL; + } + + request->num_proxied_responses++; + + /* + * Catch the most common case of everything working + * correctly. + */ + if (request->num_proxied_requests == request->num_proxied_responses) { + /* + * FIXME: remove from the event list, too? + */ + lrad_packet_list_yank(proxy_list, request->proxy); + lrad_packet_list_id_free(proxy_list, request->proxy); + request->in_proxy_hash = FALSE; + } + + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + + return request; +} + + +static void remove_from_proxy_hash(REQUEST *request) +{ + if (!request->in_proxy_hash) return; + + PTHREAD_MUTEX_LOCK(&proxy_mutex); + request->home_server->currently_outstanding--; + lrad_packet_list_yank(proxy_list, request->proxy); + lrad_packet_list_id_free(proxy_list, request->proxy); + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + + request->in_proxy_hash = FALSE; +} + + +static int insert_into_proxy_hash(REQUEST *request) +{ + int i, proxy; + char buf[128]; + + rad_assert(request->proxy != NULL); + rad_assert(proxy_list != NULL); + + request->proxy->sockfd = -1; + + PTHREAD_MUTEX_LOCK(&proxy_mutex); + + request->home_server->currently_outstanding++; + + if (!lrad_packet_list_id_alloc(proxy_list, request->proxy)) { + int found; + rad_listen_t *proxy_listener; + + /* + * Allocate a new proxy fd. This function adds it + * into the list of listeners. + */ + proxy_listener = proxy_new_listener(); + if (!proxy_listener) { + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + DEBUG2("ERROR: Failed to create a new socket for proxying requests."); + return 0; + } + + /* + * Cache it locally. + */ + found = -1; + proxy = proxy_listener->fd; + for (i = 0; i < 32; i++) { + DEBUG2("PROXY %d %d", i, proxy_fds[(proxy + i) & 0x1f]); + + /* + * Found a free entry. Save the socket, + * and remember where we saved it. + */ + if (proxy_fds[(proxy + i) & 0x1f] == -1) { + found = (proxy + i) & 0x1f; + proxy_fds[found] = proxy; + proxy_listeners[found] = proxy_listener; + break; + } + } + rad_assert(found >= 0); + + if (!lrad_packet_list_socket_add(proxy_list, proxy_listener->fd)) { + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + DEBUG2("ERROR: Failed to create a new socket for proxying requests."); + return 0; /* leak proxy_listener */ + + } + + if (!lrad_packet_list_id_alloc(proxy_list, request->proxy)) { + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + DEBUG2("ERROR: Failed to create a new socket for proxying requests."); + return 0; + } + } + rad_assert(request->proxy->sockfd >= 0); + + /* + * FIXME: Hack until we get rid of rad_listen_t, and put + * the information into the packet_list. + */ + proxy = -1; + for (i = 0; i < 32; i++) { + if (proxy_fds[i] == request->proxy->sockfd) { + proxy = i; + break; + } + } + + if (proxy < 0) { + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + DEBUG2("ERROR: All sockets are full."); + return 0; + } + + rad_assert(proxy_fds[proxy] != -1); + rad_assert(proxy_listeners[proxy] != NULL); + request->proxy_listener = proxy_listeners[proxy]; + + if (!lrad_packet_list_insert(proxy_list, &request->proxy)) { + /* FIXME: free id? */ + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + DEBUG2("ERROR: Failed to insert entry into proxy list"); + return 0; + } + + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + + DEBUG3(" proxy: allocating destination %s port %d - Id %d", + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, buf, sizeof(buf)), + request->proxy->dst_port, + request->proxy->id); + + request->in_proxy_hash = TRUE; + + return 1; +} + + +/* + * Called as BOTH an event, and in-line from other functions. + */ +static void wait_for_proxy_id_to_expire(void *ctx) +{ + REQUEST *request = ctx; + home_server *home = request->home_server; + + rad_assert(request->magic == REQUEST_MAGIC); + rad_assert(request->proxy != NULL); + + request->when = request->proxy_when; + request->when.tv_sec += home->response_window; + + if ((request->num_proxied_requests == request->num_proxied_responses) || + timercmp(&now, &request->when, >)) { + if (request->packet) { + DEBUG2("Cleaning up request %d ID %d with timestamp +%d", + request->number, request->packet->id, + (unsigned int) (request->timestamp - start_time)); + } else { + DEBUG2("Cleaning up request %d with timestamp +%d", + request->number, + (unsigned int) (request->timestamp - start_time)); + } + lrad_event_delete(el, request); + remove_from_proxy_hash(request); + remove_from_request_hash(request); + request_free(&request); + return; + } + + if (!lrad_event_insert(el, wait_for_proxy_id_to_expire, + request, &request->when)) { + rad_panic("Failed to insert event"); + } +} + + +static void wait_for_child_to_die(void *ctx) +{ + REQUEST *request = ctx; + + rad_assert(request->magic == REQUEST_MAGIC); + + if ((request->child_state == REQUEST_QUEUED) | + (request->child_state == REQUEST_RUNNING)) { + request->delay += (request->delay >> 1); + tv_add(&request->when, request->delay); + + DEBUG2("Child is still stuck for request %d", request->number); + if (!lrad_event_insert(el, wait_for_child_to_die, + request, &request->when)) { + rad_panic("Failed to insert event"); + } + return; + } + + DEBUG2("Child is finally responsive for request %d", request->number); + remove_from_request_hash(request); + + if (request->proxy) { + return wait_for_proxy_id_to_expire(request); + } + + request_free(&request); +} + + +static void cleanup_delay(void *ctx) +{ + REQUEST *request = ctx; + + rad_assert(request->magic == REQUEST_MAGIC); + rad_assert(request->child_state == REQUEST_CLEANUP_DELAY); + + remove_from_request_hash(request); + + if (request->proxy) { + return wait_for_proxy_id_to_expire(request); + } + + DEBUG2("Cleaning up request %d ID %d with timestamp +%d", + request->number, request->packet->id, + (unsigned int) (request->timestamp - start_time)); + + request_free(&request); +} + + +static void reject_delay(void *ctx) +{ + REQUEST *request = ctx; + + rad_assert(request->magic == REQUEST_MAGIC); + rad_assert(request->child_state == REQUEST_REJECT_DELAY); + + DEBUG2("Sending delayed reject for request %d", request->number); + + request->listener->send(request->listener, request); + + request->when.tv_sec += mainconfig.cleanup_delay; + request->child_state = REQUEST_CLEANUP_DELAY; + + if (!lrad_event_insert(el, cleanup_delay, + request, &request->when)) { + rad_panic("Failed to insert event"); + } +} + + +static void revive_home_server(void *ctx) +{ + home_server *home = ctx; + + home->state = HOME_STATE_ALIVE; + DEBUG2("Marking home server alive again... we have no idea if it really is alive or not."); +} + + +static void no_response_to_ping(void *ctx) +{ + REQUEST *request = ctx; + home_server *home = request->home_server; + char buffer[128]; + + home->num_received_pings = 0; + + DEBUG2("No response to ping %d from home server %s port %d", + request->number, + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), + request->proxy->dst_port); + + wait_for_proxy_id_to_expire(request); +} + + +static void received_response_to_ping(REQUEST *request) +{ + home_server *home = request->home_server; + char buffer[128]; + + home->num_received_pings++; + + DEBUG2("Received response to ping %d (%d in current sequence)", + request->number, home->num_received_pings); + + if (home->num_received_pings < home->num_pings_to_alive) { + wait_for_proxy_id_to_expire(request); + return; + } + + DEBUG2("Marking home server %s port %d alive", + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), + request->proxy->dst_port); + + if (!lrad_event_delete(el, home)) { + DEBUG2("Hmm... no event for home server, WTF?"); + } + + if (!lrad_event_delete(el, request)) { + DEBUG2("Hmm... no event for request, WTF?"); + } + + wait_for_proxy_id_to_expire(request); + + home->state = HOME_STATE_ALIVE; +} + + +static void ping_home_server(void *ctx) +{ + uint32_t jitter; + home_server *home = ctx; + REQUEST *request; + VALUE_PAIR *vp; + + if (home->state == HOME_STATE_ALIVE) { + radlog(L_INFO, "Suspicious proxy state... continuing"); + return; + } + + request = request_alloc(); + request->number = request_num_counter++; + + request->proxy = rad_alloc(1); + rad_assert(request->proxy != NULL); + + gettimeofday(&request->when, NULL); + home->when = request->when; + + if (home->ping_check == HOME_PING_CHECK_STATUS_SERVER) { + request->proxy->code = PW_STATUS_SERVER; + + vp = pairmake("Message-Authenticator", "0x00", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + } else if (home->type == HOME_TYPE_AUTH) { + request->proxy->code = PW_AUTHENTICATION_REQUEST; + + vp = pairmake("User-Name", home->ping_user_name, T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + vp = pairmake("User-Password", home->ping_user_password, T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + vp = pairmake("Service-Type", "Authenticate-Only", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + vp = pairmake("Message-Authenticator", "0x00", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + } else { + request->proxy->code = PW_ACCOUNTING_REQUEST; + + vp = pairmake("User-Name", home->ping_user_name, T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + vp = pairmake("Acct-Status-Type", "Stop", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + vp = pairmake("Acct-Session-Id", "00000000", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + vp = pairmake("Event-Timestamp", "0", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + vp->vp_date = now.tv_sec; + pairadd(&request->proxy->vps, vp); + } + + vp = pairmake("NAS-Identifier", "Ping! Are you alive?", T_OP_SET); + if (!vp) rad_panic("Out of memory"); + pairadd(&request->proxy->vps, vp); + + request->proxy->dst_ipaddr = home->ipaddr; + request->proxy->dst_port = home->port; + request->home_server = home; + + rad_assert(request->proxy_listener == NULL); + + if (!insert_into_proxy_hash(request)) { + DEBUG2("Failed inserting ping %d into proxy hash. Discarding it.", + request->number); + request_free(&request); + return; + } + rad_assert(request->proxy_listener != NULL); + request->proxy_listener->send(request->proxy_listener, + request); + + /* + * FIXME: add a separate timeout for ping packets! + */ + request->child_state = REQUEST_PROXIED; + request->when.tv_sec += mainconfig.cleanup_delay; + + if (!lrad_event_insert(el, no_response_to_ping, + request, &request->when)) { + rad_panic("Failed to insert event"); + } + + /* + * Add +/- 2s of jitter, as suggested in RFC 3539 + * and in the Issues and Fixes draft. + */ + home->when.tv_sec += home->ping_interval - 2; + + jitter = lrad_rand(); + jitter ^= (jitter >> 10); + jitter &= ((1 << 23) - 1); /* 22 bits of 1 */ + + tv_add(&home->when, jitter); + + if (!lrad_event_insert(el, ping_home_server, + home, &home->when)) { + rad_panic("Failed to insert event"); + } +} + + +/* maybe check this against wait_for_proxy_id_to_expire? */ +static void no_response_to_proxied_request(void *ctx) +{ + REQUEST *request = ctx; + home_server *home; + struct timeval when; + char buffer[128]; + + rad_assert(request->magic == REQUEST_MAGIC); + rad_assert(request->child_state == REQUEST_PROXIED); + + radlog(L_ERR, "Rejecting request %d due to lack of any response from home server %s port %d", + request->number, + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), + request->proxy->dst_port); + + if ((request->proxy->code == PW_ACCOUNTING_REQUEST) || + (request->proxy->code == PW_STATUS_SERVER)) { + remove_from_request_hash(request); + wait_for_proxy_id_to_expire(request); + + } else { + /* FIXME: run it through post-auth reject section? */ + + request->reply->code = PW_AUTHENTICATION_REJECT; + + request->listener->send(request->listener, request); + + request->child_state = REQUEST_CLEANUP_DELAY; + request->when.tv_sec += mainconfig.cleanup_delay; + + /* cleanup_delay calls wait_for_proxy_id_to_expire */ + if (!lrad_event_insert(el, cleanup_delay, + request, &request->when)) { + rad_panic("Failed to insert event"); + } + } + + home = request->home_server; + if (home->state == HOME_STATE_IS_DEAD) { + /* FIXME: assert that some event is set for the home server */ + return; + } + + /* + * Enable the zombie period when we notice that the home + * server hasn't responded. We also back-date the start + * of the zombie period to when the proxied request was + * sent. + */ + if (home->state == HOME_STATE_ALIVE) { + DEBUG2("WARNING: Home server %s port %d may be dead.", + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), + request->proxy->dst_port); + home->state = HOME_STATE_ZOMBIE; + home->zombie_period_start = now; + home->zombie_period_start.tv_sec -= home->response_window; + return; + } + + when = home->zombie_period_start; + when.tv_sec += home->zombie_period; + + if (timercmp(&now, &when, <)) { + return; + } + + /* + * It's been a zombie for too long, mark it as + * dead. + */ + home->state = HOME_STATE_IS_DEAD; + home->num_received_pings = 0; + home->when = request->when; + + if (home->ping_check != HOME_PING_CHECK_NONE) { + rad_assert((home->ping_check == HOME_PING_CHECK_STATUS_SERVER) || + (home->ping_user_name != NULL)); + home->when.tv_sec += home->ping_interval; + if (!lrad_event_insert(el, ping_home_server, + home, &home->when)) { + rad_panic("Failed to insert event"); + } + } else { + home->when.tv_sec += home->revive_interval; + if (!lrad_event_insert(el, revive_home_server, + home, &home->when)) { + rad_panic("Failed to insert event"); + } + } +} + + +static void wait_a_bit(void *ctx) +{ + struct timeval when; + REQUEST *request = ctx; + lrad_event_callback_t callback = NULL; + + rad_assert(request->magic == REQUEST_MAGIC); + + switch (request->child_state) { + case REQUEST_QUEUED: + case REQUEST_RUNNING: + when = request->received; + when.tv_sec += mainconfig.max_request_time; + + if (timercmp(&now, &when, <)) { + callback = wait_a_bit; + } else { + /* FIXME: kill unresponsive children? */ + radlog(L_ERR, "WARNING: Unresponsive child (id %lu) for request %d, in module %s component %s", + (unsigned long)request->child_pid, request->number, + request->module ? request->module : "", + request->component ? request->component : ""); + + request->master_state = REQUEST_STOP_PROCESSING; + + request->delay = 500000; + tv_add(&request->when, request->delay); + callback = wait_for_child_to_die; + } + request->delay += request->delay >> 1; + break; + + case REQUEST_PROXIED: + case REQUEST_REJECT_DELAY: + case REQUEST_CLEANUP_DELAY: + rad_assert(request->next_callback != NULL); + + request->when = request->next_when; + callback = request->next_callback; + request->next_callback = NULL; + break; + + case REQUEST_DONE: + request->child_pid = NO_SUCH_CHILD_PID; + if (request->proxy) { + return wait_for_proxy_id_to_expire(request); + } + return; + + default: + rad_panic("Internal sanity check failure"); + return; + } + +#if 0 + /* + * FIXME: Print time as "+%d.%06d", as that's more + * understandable than 32-bit numbers. + */ + DEBUG2("Inserting event for request %d at time %d.%06d", + request->number, + (int) request->when.tv_sec, (int) request->when.tv_usec); +#endif + + if (!lrad_event_insert(el, callback, + request, &request->when)) { + rad_panic("Failed to insert event"); + } +} + + +static int request_pre_handler(REQUEST *request) +{ + int rcode; + + rad_assert(request->magic == REQUEST_MAGIC); + rad_assert(request->packet != NULL); + rad_assert(request->packet->dst_port != 0); + + request->child_state = REQUEST_RUNNING; + + /* + * Don't decode the packet if it's an internal "fake" + * request. Instead, just return so that the caller can + * process it. + */ + if (request->packet->dst_port == 0) { + request->username = pairfind(request->packet->vps, + PW_USER_NAME); + request->password = pairfind(request->packet->vps, + PW_USER_PASSWORD); + return 1; + } + + /* + * Put the decoded packet into it's proper place. + */ + if (request->proxy_reply != NULL) { + rcode = request->proxy_listener->decode(request->proxy_listener, + request); + } else { + rcode = request->listener->decode(request->listener, request); + } + + if (rcode < 0) { + radlog(L_ERR, "%s Dropping packet without response.", librad_errstr); + request->child_state = REQUEST_DONE; + return 0; + } + + if (!request->proxy) { + request->username = pairfind(request->packet->vps, + PW_USER_NAME); + } else { + int post_proxy_type = 0; + VALUE_PAIR *vp; + + /* + * Delete any reply we had accumulated until now. + */ + pairfree(&request->reply->vps); + + /* + * Run the packet through the post-proxy stage, + * BEFORE playing games with the attributes. + */ + vp = pairfind(request->config_items, PW_POST_PROXY_TYPE); + if (vp) { + DEBUG2(" Found Post-Proxy-Type %s", vp->vp_strvalue); + post_proxy_type = vp->lvalue; + } + rcode = module_post_proxy(post_proxy_type, request); + + /* + * Delete the Proxy-State Attributes from the reply. + * These include Proxy-State attributes from us and + * remote server. + */ + pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE); + + /* + * Add the attributes left in the proxy reply to + * the reply list. + */ + pairadd(&request->reply->vps, request->proxy_reply->vps); + request->proxy_reply->vps = NULL; + + /* + * Free proxy request pairs. + */ + pairfree(&request->proxy->vps); + + /* + * FIXME: If the packet is an Access-Challenge, + * THEN add it to a cache, which does: + * + * (src IP, State) -> (home server ip/port) + * + * This allows the load-balancing code to + * work for EAP... + * + * Alternately, we can delete the State from the home + * server, and use our own.. that might be better. + */ + + switch (rcode) { + default: /* Don't Do Anything */ + break; + case RLM_MODULE_FAIL: + /* FIXME: debug print stuff */ + request->child_state = REQUEST_DONE; + return 0; + + case RLM_MODULE_HANDLED: + /* FIXME: debug print stuff */ + request->child_state = REQUEST_DONE; + return 0; + } + } + + return 1; +} + + +/* + * Return 1 if we did proxy it, or the proxy attempt failed + * completely. Either way, the caller doesn't touch the request + * any more if we return 1. + */ +static int successfully_proxied_request(REQUEST *request) +{ + int rcode; + int pre_proxy_type = 0; + VALUE_PAIR *realmpair; + VALUE_PAIR *strippedname; + VALUE_PAIR *vp; + char *realmname; + home_server *home; + struct timeval when; + REALM *realm = NULL; + char buffer[128]; + + realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM); + if (!realmpair || + (realmpair->length == 0)) { + return 0; + } + + realmname = (char *) realmpair->vp_strvalue; + + realm = realm_find(realmname); + if (!realm) { + DEBUG2("ERROR: Cannot proxy to unknown realm %s", + realmname); + reject_request: + /* + * Failed proxying means silently discard + * accounting responses. + */ + if (request->packet->code == PW_ACCOUNTING_REQUEST) { + request->child_state = REQUEST_DONE; + return 1; + } + + rad_assert(request->packet->code == PW_AUTHENTICATION_REQUEST); + + request->reply->code = PW_AUTHENTICATION_REJECT; + + return 0; + } + + if (((request->packet->code == PW_AUTHENTICATION_REQUEST) && + !realm->auth_pool) || + ((request->packet->code == PW_ACCOUNTING_REQUEST) && + !realm->acct_pool)) { + DEBUG2(" WARNING: Cancelling proxy to Realm %s, as the realm is local.", + realmname); + return 0; + } + + home = home_server_ldb(realm, request->packet->code); + if (!home) { + DEBUG2("ERROR: Failed to find live home server for realm %s", + realmname); + goto reject_request; + } + + /* + * Remember that we sent the request to a Realm. + */ + pairadd(&request->packet->vps, + pairmake("Realm", realmname, T_OP_EQ)); + + /* + * We read the packet from a detail file, AND it came from + * the server we're about to send it to. Don't do that. + */ + if ((request->packet->code == PW_ACCOUNTING_REQUEST) && + (request->listener->type == RAD_LISTEN_DETAIL) && + (home->ipaddr.af == AF_INET) && + (request->packet->src_ipaddr.af == AF_INET) && + (home->ipaddr.ipaddr.ip4addr.s_addr == request->packet->src_ipaddr.ipaddr.ip4addr.s_addr)) { + DEBUG2(" rlm_realm: Packet came from realm %s, proxy cancelled", realmname); + return 0; + } + + /* + * Allocate the proxy packet, only if it wasn't already + * allocated by a module. This check is mainly to support + * the proxying of EAP-TTLS and EAP-PEAP tunneled requests. + * + * In those cases, the EAP module creates a "fake" + * request, and recursively passes it through the + * authentication stage of the server. The module then + * checks if the request was supposed to be proxied, and + * if so, creates a proxy packet from the TUNNELED request, + * and not from the EAP request outside of the tunnel. + * + * The proxy then works like normal, except that the response + * packet is "eaten" by the EAP module, and encapsulated into + * an EAP packet. + */ + if (!request->proxy) { + if ((request->proxy = rad_alloc(TRUE)) == NULL) { + radlog(L_ERR|L_CONS, "no memory"); + exit(1); + } + + /* + * Copy the request, then look up name and + * plain-text password in the copy. + * + * Note that the User-Name attribute is the + * *original* as sent over by the client. The + * Stripped-User-Name attribute is the one hacked + * through the 'hints' file. + */ + request->proxy->vps = paircopy(request->packet->vps); + } + + /* + * Strip the name, if told to. + * + * Doing it here catches the case of proxied tunneled + * requests. + */ + if (realm->striprealm == TRUE && + (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME)) != NULL) { + /* + * If there's a Stripped-User-Name attribute in + * the request, then use THAT as the User-Name + * for the proxied request, instead of the + * original name. + * + * This is done by making a copy of the + * Stripped-User-Name attribute, turning it into + * a User-Name attribute, deleting the + * Stripped-User-Name and User-Name attributes + * from the vps list, and making the new + * User-Name the head of the vps list. + */ + vp = pairfind(request->proxy->vps, PW_USER_NAME); + if (!vp) { + vp = paircreate(PW_USER_NAME, PW_TYPE_STRING); + if (!vp) { + radlog(L_ERR|L_CONS, "no memory"); + exit(1); + } + vp->next = request->proxy->vps; + request->proxy->vps = vp; + } + memcpy(vp->vp_strvalue, strippedname->vp_strvalue, + sizeof(vp->vp_strvalue)); + vp->length = strippedname->length; + + /* + * Do NOT delete Stripped-User-Name. + */ + } + + /* + * If there is no PW_CHAP_CHALLENGE attribute but + * there is a PW_CHAP_PASSWORD we need to add it + * since we can't use the request authenticator + * anymore - we changed it. + */ + if (pairfind(request->proxy->vps, PW_CHAP_PASSWORD) && + pairfind(request->proxy->vps, PW_CHAP_CHALLENGE) == NULL) { + vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING); + if (!vp) { + radlog(L_ERR|L_CONS, "no memory"); + exit(1); + } + vp->length = AUTH_VECTOR_LEN; + memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN); + pairadd(&(request->proxy->vps), vp); + } + + /* + * The RFC's say we have to do this, but FreeRADIUS + * doesn't need it. + */ + vp = paircreate(PW_PROXY_STATE, PW_TYPE_STRING); + if (!vp) { + radlog(L_ERR|L_CONS, "no memory"); + exit(1); + } + sprintf(vp->vp_strvalue, "%d", request->packet->id); + vp->length = strlen(vp->vp_strvalue); + + pairadd(&request->proxy->vps, vp); + + request->proxy->code = request->packet->code; + request->proxy->dst_ipaddr = home->ipaddr; + request->proxy->dst_port = home->port; + request->home_server = home; + + /* + * Call the pre-proxy routines. + */ + vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE); + if (vp) { + DEBUG2(" Found Pre-Proxy-Type %s", vp->vp_strvalue); + pre_proxy_type = vp->lvalue; + } + rcode = module_pre_proxy(pre_proxy_type, request); + switch (rcode) { + case RLM_MODULE_FAIL: + case RLM_MODULE_INVALID: + case RLM_MODULE_NOTFOUND: + case RLM_MODULE_REJECT: + case RLM_MODULE_USERLOCK: + default: + /* FIXME: debug print failed stuff */ + goto reject_request; + + case RLM_MODULE_HANDLED: + return 0; + + /* + * Only proxy the packet if the pre-proxy code succeeded. + */ + case RLM_MODULE_NOOP: + case RLM_MODULE_OK: + case RLM_MODULE_UPDATED: + break; + } + + /* + * If it's a fake request, don't send the proxy + * packet. The outer tunnel session will take + * care of doing that. + */ + if (request->packet->dst_port == 0) { + return 1; + } + + if (!insert_into_proxy_hash(request)) { + DEBUG("ERROR: Failed to proxy request %d", request->number); + goto reject_request; + } + + request->proxy_listener->encode(request->proxy_listener, request); + + when = request->received; + when.tv_sec += mainconfig.max_request_time; + + gettimeofday(&request->proxy_when, NULL); + + request->next_when = request->proxy_when; + request->next_when.tv_sec += home->response_window; + + rad_assert(home->response_window > 0); + + if (timercmp(&when, &request->next_when, <)) { + request->next_when = when; + } + request->next_callback = no_response_to_proxied_request; + + DEBUG2("Proxying request %d to realm %s, home server %s port %d", + request->number, realmname, + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), + request->proxy->dst_port); + + /* + * Note that we set proxied AFTER creating the packet, + * but BEFORE actually sending it. + * + * Once we send it, the request is tainted, as + * another thread may have picked it up. Don't + * touch it! + */ + request->num_proxied_requests = 1; + request->num_proxied_responses = 0; + request->child_state = REQUEST_PROXIED; + request->proxy_listener->send(request->proxy_listener, + request); + return 1; +} + + +static void request_post_handler(REQUEST *request) +{ + int child_state = -1; + struct timeval when; + VALUE_PAIR *vp; + + if (request->master_state == REQUEST_STOP_PROCESSING) { + DEBUG2("Request %d was cancelled.", request->number); + request->child_state = REQUEST_DONE; + return; + } + + if (request->child_state != REQUEST_RUNNING) { + rad_panic("Internal sanity check failed"); + } + + /* + * FIXME: check for Auth-Type = Reject, and reject it if so? + * this means don't proxy it. + * + * FIXME: check if we can't proxy it because all of the + * home servers are dead or busy, and set post-auth-type + * "proxy fail", or something like that. + */ + + if (mainconfig.proxy_requests && + (request->packet->code != PW_STATUS_SERVER) && + (request->reply->code == 0) && + !request->proxy && + successfully_proxied_request(request)) { + return; + } + + /* + * Fake requests don't get encoded or signed. The caller + * also requires the reply VP's, so we don't free them + * here! + */ + if (request->packet->dst_port == 0) { + /* FIXME: DEBUG going to the next request */ + request->child_state = REQUEST_DONE; + return; + } + + /* + * Copy Proxy-State from the request to the reply. + */ + vp = paircopy2(request->packet->vps, PW_PROXY_STATE); + if (vp) pairadd(&request->reply->vps, vp); + + /* + * Access-Requests get delayed or cached. + */ + if (request->packet->code == PW_AUTHENTICATION_REQUEST) { + gettimeofday(&request->next_when, NULL); + + if (request->reply->code == 0) { + DEBUG2("There was no response configured: rejecting request %d", + request->number); + request->reply->code = PW_AUTHENTICATION_REJECT; + } + + /* + * If configured, delay Access-Reject packets. + */ + if ((request->reply->code == PW_AUTHENTICATION_REJECT) && + (mainconfig.reject_delay > 0)) { + when = request->received; + when.tv_sec += mainconfig.reject_delay; + + if (timercmp(&when, &request->next_when, >)) { + DEBUG2("Delaying reject of request %d for %d seconds", + request->number, + mainconfig.reject_delay); + request->next_when = when; + request->next_callback = reject_delay; + request->child_state = REQUEST_REJECT_DELAY; + return; + } + } + + request->next_when.tv_sec += mainconfig.cleanup_delay; + request->next_callback = cleanup_delay; + child_state = REQUEST_CLEANUP_DELAY; + + } else if (request->packet->code == PW_ACCOUNTING_REQUEST) { + request->next_callback = NULL; /* just to be safe */ + child_state = REQUEST_DONE; + + /* + * FIXME: Status-Server should probably not be + * handled here... + */ + } else if (request->packet->code == PW_STATUS_SERVER) { + request->next_callback = NULL; + child_state = REQUEST_DONE; + + } else { + rad_panic("Unknown packet type"); + } + + /* + * Encode, sign, and send. The accounting request + * handler takes care of suppressing responses when + * request->reply->code == 0. + */ + request->listener->send(request->listener, request); + + /* + * Clean up. These are no longer needed. + */ + pairfree(&request->config_items); + + pairfree(&request->packet->vps); + request->username = NULL; + request->password = NULL; + + pairfree(&request->reply->vps); + + if (request->proxy) { + pairfree(&request->proxy->vps); + } + if (request->proxy_reply) { + pairfree(&request->proxy_reply->vps); + } + + DEBUG2("Finished request %d state %d", request->number, child_state); + + request->child_state = child_state; +} + + +static void received_retransmit(REQUEST *request, const RADCLIENT *client) +{ + char buffer[128]; + + switch (request->child_state) { + case REQUEST_QUEUED: + case REQUEST_RUNNING: + radlog(L_ERR, "Discarding duplicate request from " + "client %s port %d - ID: %d due to unfinished request %d", + client->shortname, + request->packet->src_port,request->packet->id, + request->number); + break; + + case REQUEST_PROXIED: + DEBUG2("Sending duplicate proxied request to home server %s port %d - ID: %d", + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + buffer, sizeof(buffer)), + request->proxy->dst_port, + request->proxy->id); + request->num_proxied_requests++; + request->proxy_listener->send(request->proxy_listener, + request); + break; + + case REQUEST_REJECT_DELAY: + DEBUG2("Waiting to send Access-Reject " + "to client %s port %d - ID: %d", + client->shortname, + request->packet->src_port, request->packet->id); + break; + + case REQUEST_CLEANUP_DELAY: + case REQUEST_DONE: + DEBUG2("Sending duplicate reply " + "to client %s port %d - ID: %d", + client->shortname, + request->packet->src_port, request->packet->id); + request->listener->send(request->listener, request); + break; + } +} + + +static void received_conflicting_request(REQUEST *request, + const RADCLIENT *client) +{ + radlog(L_ERR, "Received conflicting packet from " + "client %s port %d - ID: %d due to unfinished request %d. Giving up on old request.", + client->shortname, + request->packet->src_port, request->packet->id, + request->number); + + switch (request->child_state) { + case REQUEST_QUEUED: + case REQUEST_RUNNING: + request->master_state = REQUEST_STOP_PROCESSING; + request->delay += request->delay >> 1; + + tv_add(&request->when, request->delay); + + if (!lrad_event_insert(el, + wait_for_child_to_die, + request, &request->when)) { + rad_panic("Failed to insert event"); + } + return; + + default: + break; + } + + remove_from_request_hash(request); + + /* + * The request stays in the event queue. At some point, + * the child will notice, and we can then delete it. + */ +} + + +static int can_handle_new_request(RADIUS_PACKET *packet, + const RADCLIENT *client) +{ + /* + * Count the total number of requests, to see if + * there are too many. If so, return with an + * error. + */ + if (mainconfig.max_requests) { + int request_count = lrad_packet_list_num_elements(pl); + + /* + * This is a new request. Let's see if + * it makes us go over our configured + * bounds. + */ + if (request_count > mainconfig.max_requests) { + radlog(L_ERR, "Dropping request (%d is too many): " + "from client %s port %d - ID: %d", request_count, + client->shortname, + packet->src_port, packet->id); + radlog(L_INFO, "WARNING: Please check the %s file.\n" + "\tThe value for 'max_requests' is probably set too low.\n", mainconfig.radiusd_conf); + return 0; + } /* else there were a small number of requests */ + } /* else there was no configured limit for requests */ + + /* + * FIXME: Add per-client checks. If one client is sending + * too many packets, start discarding them. + * + * We increment the counters here, and decrement them + * when the response is sent... somewhere in this file. + */ + + /* + * FUTURE: Add checks for system load. If the system is + * busy, start dropping requests... + * + * We can probably keep some statistics ourselves... if + * there are more requests coming in than we can handle, + * start dropping some. + */ + + return 1; +} + + +int received_request(rad_listen_t *listener, + RADIUS_PACKET *packet, REQUEST **prequest, + const RADCLIENT *client) +{ + RADIUS_PACKET **packet_p; + REQUEST *request = NULL; + + packet_p = lrad_packet_list_find(pl, packet); + if (packet_p) { + request = lrad_packet2myptr(REQUEST, packet, packet_p); + + if (memcmp(request->packet->vector, packet->vector, + sizeof(packet->vector)) == 0) { + received_retransmit(request, client); + return 0; + } + received_conflicting_request(request, client); + request = NULL; + + } else if (!can_handle_new_request(packet, client)) { + return 0; + } + + /* + * Create and initialize the new request. + */ + request = request_alloc(); /* never fails */ + + if ((request->reply = rad_alloc(0)) == NULL) { + radlog(L_ERR, "No memory"); + exit(1); + } + + request->listener = listener; + request->packet = packet; + request->packet->timestamp = request->timestamp; + request->number = request_num_counter++; + strlcpy(request->secret, client->secret, sizeof(request->secret)); + + /* + * Remember the request in the list. + */ + if (!lrad_packet_list_insert(pl, &request->packet)) { + radlog(L_ERR, "Failed to insert request %d in the list of live requests: discarding", request->number); + request_free(&request); + return 0; + } + request->in_request_hash = TRUE; + + /* + * The request passes many of our sanity checks. + * From here on in, if anything goes wrong, we + * send a reject message, instead of dropping the + * packet. + */ + + /* + * Build the reply template from the request. + */ + + request->reply->sockfd = request->packet->sockfd; + request->reply->dst_ipaddr = request->packet->src_ipaddr; + request->reply->src_ipaddr = request->packet->dst_ipaddr; + request->reply->dst_port = request->packet->src_port; + request->reply->src_port = request->packet->dst_port; + request->reply->id = request->packet->id; + request->reply->code = 0; /* UNKNOWN code */ + memcpy(request->reply->vector, request->packet->vector, + sizeof(request->reply->vector)); + request->reply->vps = NULL; + request->reply->data = NULL; + request->reply->data_len = 0; + + request->master_state = REQUEST_ACTIVE; + request->child_state = REQUEST_QUEUED; + request->next_callback = NULL; + + gettimeofday(&request->received, NULL); + request->when = request->received; + request->delay = USEC / 10; + + tv_add(&request->when, request->delay); + + if (!lrad_event_insert(el, wait_a_bit, + request, &request->when)) { + rad_panic("Failed to insert event"); + } + + *prequest = request; + return 1; +} + + +REQUEST *received_proxy_response(RADIUS_PACKET *packet) +{ + char buffer[128]; + home_server *home; + REQUEST *request; + + if (!home_server_find(&packet->src_ipaddr, packet->src_port)) { + radlog(L_ERR, "Ignoring request from unknown home server %s port %d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port); + rad_free(&packet); + return NULL; + } + + /* + * Also removes from the proxy hash if responses == requests + */ + request = lookup_in_proxy_hash(packet); + + if (!request) { + radlog(L_PROXY, "No outstanding request was found for proxy reply from home server %s port %d - ID %d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port, packet->id); + rad_free(&packet); + return NULL; + } + + home = request->home_server; + + gettimeofday(&now, NULL); + home->state = HOME_STATE_ALIVE; + + if (request->reply && request->reply->code != 0) { + DEBUG2("We already replied to this request. Discarding response from home server."); + rad_free(&packet); + return NULL; + } + + /* + * We had previously received a reply, so we don't need + * to do anything here. + */ + if (request->proxy_reply) { + if (memcmp(request->proxy_reply->vector, + packet->vector, + sizeof(request->proxy_reply->vector)) == 0) { + DEBUG2("Discarding duplicate reply from home server %s port %d - ID: %d for request %d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port, packet->id, + request->number); + } else { + /* + * ? The home server gave us a new proxy + * reply, which doesn't match the old + * one. Delete it. + */ + DEBUG2("Ignoring conflicting proxy reply"); + } + + /* assert that there's an event queued for request? */ + rad_free(&packet); + return NULL; + } + + switch (request->child_state) { + case REQUEST_QUEUED: + case REQUEST_RUNNING: + rad_panic("Internal sanity check failed for child state"); + break; + + case REQUEST_REJECT_DELAY: + case REQUEST_CLEANUP_DELAY: + case REQUEST_DONE: + radlog(L_ERR, "Reply from home server %s port %d - ID: %d arrived too late for request %d. Try increasing 'retry_delay' or 'max_request_time'", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + buffer, sizeof(buffer)), + packet->src_port, packet->id, + request->number); + /* assert that there's an event queued for request? */ + rad_free(&packet); + return NULL; + + case REQUEST_PROXIED: + break; + } + + request->proxy_reply = packet; + + /* + * There's no incoming request, so it's a proxied packet + * we originated. + */ + if (!request->packet) { + received_response_to_ping(request); + return NULL; + } + + request->child_state = REQUEST_QUEUED; + request->when = now; + request->delay = USEC / 10; + tv_add(&request->when, request->delay); + + /* + * Wait a bit will take care of max_request_time + */ + if (!lrad_event_insert(el, wait_a_bit, + request, &request->when)) { + rad_panic("Failed to insert event"); + } + + return request; +} + + +/* + * Externally-visibly functions. + */ +int radius_event_init(int spawn_flag) +{ + if (el) return 0; + + time(&start_time); + + el = lrad_event_list_create(); + if (!el) return 0; + + pl = lrad_packet_list_create(0); + if (!el) return 0; + + request_num_counter = 0; + + /* + * Move all of the thread calls to this file? + * + * It may be best for the mutexes to be in this file... + */ + have_children = spawn_flag; + + if (mainconfig.proxy_requests) { + int i; + rad_listen_t *listener; + + /* + * Create the tree for managing proxied requests and + * responses. + */ + proxy_list = lrad_packet_list_create(1); + if (!proxy_list) return 0; + +#ifdef HAVE_PTHREAD_H + if (pthread_mutex_init(&proxy_mutex, NULL) != 0) { + radlog(L_ERR, "FATAL: Failed to initialize proxy mutex: %s", + strerror(errno)); + exit(1); + } +#endif + + /* + * Mark the Fd's as unused. + */ + for (i = 0; i < 32; i++) proxy_fds[i] = -1; + + i = -1; + + for (listener = mainconfig.listen; + listener != NULL; + listener = listener->next) { + if (listener->type == RAD_LISTEN_PROXY) { + /* + * FIXME: This works only because we + * start off with one proxy socket. + */ + rad_assert(proxy_fds[listener->fd & 0x1f] == -1); + rad_assert(proxy_listeners[listener->fd & 0x1f] == NULL); + + proxy_fds[listener->fd & 0x1f] = listener->fd; + proxy_listeners[listener->fd & 0x1f] = listener; + if (!lrad_packet_list_socket_add(proxy_list, listener->fd)) { + rad_assert(0 == 1); + } + i = listener->fd; + } + } + + if (mainconfig.proxy_requests) rad_assert(i >= 0); + } + + thread_pool_init(spawn_flag); + + return 1; +} + + +static int request_hash_cb(void *ctx, void *data) +{ + ctx = ctx; /* -Wunused */ + REQUEST *request = lrad_packet2myptr(REQUEST, packet, data); + + rad_assert(request->in_proxy_hash == FALSE); + + lrad_event_delete(el, request); + remove_from_request_hash(request); + request_free(&request); + + return 0; +} + + +static int proxy_hash_cb(void *ctx, void *data) +{ + ctx = ctx; /* -Wunused */ + REQUEST *request = lrad_packet2myptr(REQUEST, proxy, data); + + lrad_packet_list_yank(proxy_list, request->proxy); + request->in_proxy_hash = FALSE; + + if (!request->in_request_hash) { + lrad_event_delete(el, request); + request_free(&request); + } + + return 0; +} + + +void radius_event_free(void) +{ + /* + * FIXME: Stop all threads, or at least check that + * they're all waiting on the semaphore, and the queues + * are empty. + */ + + /* + * There are requests in the proxy hash that aren't + * referenced from anywhere else. Remove them first. + */ + if (proxy_list) { + PTHREAD_MUTEX_LOCK(&proxy_mutex); + lrad_packet_list_walk(proxy_list, NULL, proxy_hash_cb); + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + lrad_packet_list_free(proxy_list); + proxy_list = NULL; + } + + lrad_packet_list_walk(pl, NULL, request_hash_cb); + + lrad_packet_list_free(pl); + pl = NULL; + + lrad_event_list_free(el); +} + +int radius_event_process(struct timeval **pptv) +{ + int rcode; + struct timeval when; + + if (!el) return 0; + + if (lrad_event_list_num_elements(el) == 0) { + *pptv = NULL; + return 1; + } + + gettimeofday(&now, NULL); + when = now; + + do { + rcode = lrad_event_run(el, &when); + } while (rcode == 1); + + gettimeofday(&now, NULL); + + if ((when.tv_sec == 0) && (when.tv_usec == 0)) { + if (lrad_event_list_num_elements(el) == 0) { + *pptv = NULL; + return 1; + } + rad_panic("Internal sanity check failed"); + + } else if (timercmp(&now, &when, >)) { + DEBUG2("WTF?"); + when.tv_sec = 0; + when.tv_usec = 1; + + } else { + when.tv_sec -= now.tv_sec; + when.tv_usec -= now.tv_usec; + if (when.tv_usec < 0) { + when.tv_sec--; + when.tv_usec += USEC; + } + } + **pptv = when; + + return 1; +} + +void radius_handle_request(REQUEST *request, RAD_REQUEST_FUNP fun) +{ + if (!request_pre_handler(request)) { + DEBUG2("Going to the next request at X"); + return; + } + + rad_assert(fun != NULL); + rad_assert(request != NULL); + + fun(request); + + request_post_handler(request); + DEBUG2("Going to the next request"); + return; +} + diff --git a/src/main/files.c b/src/main/files.c index cf4d24e..b409baf 100644 --- a/src/main/files.c +++ b/src/main/files.c @@ -301,371 +301,3 @@ static void debug_pair_list(PAIR_LIST *pl) } } #endif - -#ifndef BUILDDBM /* HACK HACK */ - -/* - * Free a REALM list. - */ -void realm_free(REALM *cl) -{ - REALM *next; - - while(cl) { - next = cl->next; - free(cl); - cl = next; - } -} - -/* - * Read the realms file. - */ -int read_realms_file(const char *file) -{ - FILE *fp; - char buffer[256]; - char realm[256]; - char hostnm[256]; - char opts[256]; - char *s, *p; - int lineno = 0; - REALM *c, **tail; - int got_realm = FALSE; - - realm_free(mainconfig.realms); - mainconfig.realms = NULL; - tail = &mainconfig.realms; - - if ((fp = fopen(file, "r")) == NULL) { - /* The realms file is not mandatory. If it exists it will - be used, however, since the new style config files are - more robust and flexible they are more likely to get used. - So this is a non-fatal error. */ - return 0; - } - - while(fgets(buffer, 256, fp) != NULL) { - lineno++; - if (!feof(fp) && (strchr(buffer, '\n') == NULL)) { - radlog(L_ERR, "%s[%d]: line too long", file, lineno); - return -1; - } - if (buffer[0] == '#' || buffer[0] == '\n') - continue; - p = buffer; - if (!getword(&p, realm, sizeof(realm)) || - !getword(&p, hostnm, sizeof(hostnm))) { - radlog(L_ERR, "%s[%d]: syntax error", file, lineno); - continue; - } - - got_realm = TRUE; - c = rad_malloc(sizeof(REALM)); - memset(c, 0, sizeof(REALM)); - - if ((s = strchr(hostnm, ':')) != NULL) { - *s++ = 0; - c->auth_port = atoi(s); - c->acct_port = c->auth_port + 1; - } else { - c->auth_port = PW_AUTH_UDP_PORT; - c->acct_port = PW_ACCT_UDP_PORT; - } - - if (strcmp(hostnm, "LOCAL") == 0) { - /* - * Local realms don't have an IP address, - * secret, or port. - */ - c->ipaddr.af = c->acct_ipaddr.af = AF_INET; - c->ipaddr.ipaddr.ip4addr.s_addr = c->acct_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE); - c->secret[0] = '\0'; - c->auth_port = 0; - c->acct_port = 0; - - } else { - RADCLIENT *client; - - if (ip_hton(hostnm, AF_INET, &c->ipaddr) < 0) { - radlog(L_CONS|L_ERR, "%s[%d]: Failed to look up hostname %s", - file, lineno, hostnm); - return -1; - } - c->acct_ipaddr = c->ipaddr; - - /* - * Find the remote server in the "clients" list. - * If we can't find it, there's a big problem... - */ - client = client_find_old(&c->ipaddr); - if (client == NULL) { - radlog(L_CONS|L_ERR, "%s[%d]: Cannot find 'clients' file entry of remote server %s for realm \"%s\"", - file, lineno, hostnm, realm); - return -1; - } - memcpy(c->secret, client->secret, sizeof(c->secret)); - } - - /* - * Double-check lengths to be sure they're sane - */ - if (strlen(hostnm) >= sizeof(c->server)) { - radlog(L_ERR, "%s[%d]: server name of length %u is greater than the allowed maximum of %u.", - file, lineno, - strlen(hostnm), - sizeof(c->server) - 1); - return -1; - } - if (strlen(realm) > sizeof(c->realm)) { - radlog(L_ERR, "%s[%d]: realm of length %u is greater than the allowed maximum of %u.", - file, lineno, - strlen(realm), - sizeof(c->realm) - 1); - return -1; - } - - /* - * OK, they're sane, copy them over. - */ - strcpy(c->realm, realm); - strcpy(c->server, hostnm); - c->striprealm = TRUE; - c->active = TRUE; - c->acct_active = TRUE; - - while (getword(&p, opts, sizeof(opts))) { - if (strcmp(opts, "nostrip") == 0) - c->striprealm = FALSE; - if (strstr(opts, "noacct") != NULL) - c->acct_port = 0; - if (strstr(opts, "trusted") != NULL) - c->trusted = 1; - if (strstr(opts, "notrealm") != NULL) - c->notrealm = 1; - if (strstr(opts, "notsuffix") != NULL) - c->notrealm = 1; - } - - c->next = NULL; - *tail = c; - tail = &c->next; - } - fclose(fp); - - /* - * Complain only if the realms file has content. - */ - if (got_realm) { - radlog(L_INFO, "Using deprecated realms file. Support for this will go away soon."); - } - - return 0; -} -#endif /* BUILDDBM */ - -/* - * Mark a host inactive - */ -void realm_disable(REQUEST *request) -{ - REALM *cl; - time_t now; - lrad_ipaddr_t *ipaddr; - int port; - - now = time(NULL); - - if (!request || !request->proxy || - (request->proxy->dst_ipaddr.af != AF_INET)) return; - - ipaddr = &request->proxy->dst_ipaddr; - port = request->proxy->dst_port; - - for (cl = mainconfig.realms; cl; cl = cl->next) { - if (ipaddr->af != cl->ipaddr.af) continue; - - /* - * FIXME: Switch over AF - */ - if ((ipaddr->ipaddr.ip4addr.s_addr == cl->ipaddr.ipaddr.ip4addr.s_addr) && - (port == cl->auth_port)) { - /* - * If we've received a reply (any reply) - * from the home server in the time spent - * re-sending this request, then don't mark - * the realm as dead. - */ - if (cl->last_reply > (( now - mainconfig.proxy_retry_delay * mainconfig.proxy_retry_count ))) { - continue; - } - - cl->active = FALSE; - cl->wakeup = now + mainconfig.proxy_dead_time; - radlog(L_PROXY, "marking authentication server %s:%d for realm %s dead", - cl->server, port, cl->realm); - } else if ((ipaddr->ipaddr.ip4addr.s_addr == cl->acct_ipaddr.ipaddr.ip4addr.s_addr) && - (port == cl->acct_port)) { - if (cl->last_reply > (( now - mainconfig.proxy_retry_delay * mainconfig.proxy_retry_count ))) { - continue; - } - - cl->acct_active = FALSE; - cl->acct_wakeup = now + mainconfig.proxy_dead_time; - radlog(L_PROXY, "marking accounting server %s:%d for realm %s dead", - cl->acct_server, port, cl->realm); - } - } -} - -/* - * Find a realm in the REALM list. - */ -REALM *realm_find(const char *realm, int accounting) -{ - REALM *cl; - REALM *default_realm = NULL; - time_t now; - int dead_match = 0; - - now = time(NULL); - - /* - * If we're passed a NULL realm pointer, - * then look for a "NULL" realm string. - */ - if (realm == NULL) { - realm = "NULL"; - } - - for (cl = mainconfig.realms; cl; cl = cl->next) { - /* - * Wake up any sleeping realm. - */ - if (cl->wakeup <= now) { - cl->active = TRUE; - } - if (cl->acct_wakeup <= now) { - cl->acct_active = TRUE; - } - - /* - * Asked for auth/acct, and the auth/acct server - * is not active. Skip it. - */ - if ((!accounting && !cl->active) || - (accounting && !cl->acct_active)) { - - /* - * We've been asked to NOT fall through - * to the DEFAULT realm if there are - * exact matches for this realm which are - * dead. - */ - if ((!mainconfig.proxy_fallback) && - (strcasecmp(cl->realm, realm) == 0)) { - dead_match = 1; - } - continue; - } - - /* - * If it matches exactly, return it. - * - * Note that we just want ONE live realm - * here. We don't care about round-robin, or - * scatter techniques, as that's more properly - * the responsibility of the proxying code. - */ - if (strcasecmp(cl->realm, realm) == 0) { - return cl; - } - - /* - * No default realm, try to set one. - */ - if ((default_realm == NULL) && - (strcmp(cl->realm, "DEFAULT") == 0)) { - default_realm = cl; - } - } /* loop over all realms */ - - /* - * There WAS one or more matches which were marked dead, - * AND there were NO live matches, AND we've been asked - * to NOT fall through to the DEFAULT realm. Therefore, - * we return NULL, which means "no match found". - */ - if (!mainconfig.proxy_fallback && dead_match) { - if (mainconfig.wake_all_if_all_dead) { - REALM *rcl = NULL; - for (cl = mainconfig.realms; cl; cl = cl->next) { - if(strcasecmp(cl->realm,realm) == 0) { - if (!accounting && !cl->active) { - cl->active = TRUE; - rcl = cl; - } - else if (accounting && - !cl->acct_active) { - cl->acct_active = TRUE; - rcl = cl; - } - } - } - return rcl; - } - else { - return NULL; - } - } - - /* If we didn't find the realm 'NULL' don't return the - * DEFAULT entry. - */ - if ((strcmp(realm, "NULL")) == 0) { - return NULL; - } - - /* - * Didn't find anything that matched exactly, return the - * DEFAULT realm. We also return the DEFAULT realm if - * all matching realms were marked dead, and we were - * asked to fall through to the DEFAULT realm in this - * case. - */ - return default_realm; -} - -/* - * Find a realm for a proxy reply by proxy's IP - * - * Note that we don't do anything else. - */ -REALM *realm_findbyaddr(uint32_t ipaddr, int port) -{ - REALM *cl; - - /* - * Note that we do NOT check for inactive realms! - * - * The purpose of this code is simply to find a matching - * source IP/Port pair, for a home server which is allowed - * to send us proxy replies. If we get a reply, then it - * doesn't matter if we think the realm is inactive. - */ - for (cl = mainconfig.realms; cl != NULL; cl = cl->next) { - if (cl->ipaddr.af != AF_INET) rad_assert(0 == 1); - - if ((ipaddr == cl->ipaddr.ipaddr.ip4addr.s_addr) && - (port == cl->auth_port)) { - return cl; - - } else if ((ipaddr == cl->acct_ipaddr.ipaddr.ip4addr.s_addr) && - (port == cl->acct_port)) { - return cl; - } - } - - return NULL; -} - diff --git a/src/main/listen.c b/src/main/listen.c index 393e3fa..d6f879f 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -61,7 +61,6 @@ RCSID("$Id$") #include #include -#include static time_t start_time = 0; @@ -81,8 +80,9 @@ typedef struct rad_listen_master_t { rad_listen_free_t free; rad_listen_recv_t recv; rad_listen_send_t send; - rad_listen_update_t update; rad_listen_print_t print; + rad_listen_encode_t encode; + rad_listen_decode_t decode; } rad_listen_master_t; typedef struct listen_socket_t { @@ -182,266 +182,6 @@ static int rad_status_server(REQUEST *request) return 0; } -static int request_num_counter = 0; - -/* - * Check for dups, etc. Common to Access-Request && - * Accounting-Request packets. - */ -static int common_checks(rad_listen_t *listener, - RADIUS_PACKET *packet, REQUEST **prequest, - const RADCLIENT *client) -{ - REQUEST *curreq; - char buffer[128]; - - rad_assert(listener->rl != NULL); - - /* - * If there is no existing request of id, code, etc., - * then we can return, and let it be processed. - */ - if ((curreq = rl_find(listener->rl, packet)) == NULL) { - /* - * Count the total number of requests, to see if - * there are too many. If so, return with an - * error. - */ - if (mainconfig.max_requests) { - /* - * FIXME: This is now per-socket, - * when it should really be global - * to the server! - */ - int request_count = rl_num_requests(listener->rl); - - /* - * This is a new request. Let's see if - * it makes us go over our configured - * bounds. - */ - if (request_count > mainconfig.max_requests) { - radlog(L_ERR, "Dropping request (%d is too many): " - "from client %s port %d - ID: %d", request_count, - client->shortname, - packet->src_port, packet->id); - radlog(L_INFO, "WARNING: Please check the %s file.\n" - "\tThe value for 'max_requests' is probably set too low.\n", mainconfig.radiusd_conf); - return 0; - } /* else there were a small number of requests */ - } /* else there was no configured limit for requests */ - - /* - * FIXME: Add checks for system load. If the - * system is busy, start dropping requests... - * - * We can probably keep some statistics - * ourselves... if there are more requests - * coming in than we can handle, start dropping - * some. - */ - - /* - * The current request isn't finished, which - * means that the NAS sent us a new packet, while - * we are still processing the old request. - */ - } else if (!curreq->finished) { - /* - * If the authentication vectors are identical, - * then the NAS is re-transmitting it, trying to - * kick us into responding to the request. - */ - if (memcmp(curreq->packet->vector, packet->vector, - sizeof(packet->vector)) == 0) { - RAD_SNMP_INC(rad_snmp.auth.total_dup_requests); - - /* - * It's not finished because the request - * was proxied, but there was no reply - * from the home server. - * - * This code will never get hit for - * accounting packets, as they're always - * updated, and never re-transmitted. - */ - if (curreq->proxy && !curreq->proxy_reply) { - DEBUG2("Sending duplicate proxied request to home server %s port %d - ID: %d", - inet_ntop(curreq->proxy->dst_ipaddr.af, - &curreq->proxy->dst_ipaddr.ipaddr, - buffer, sizeof(buffer)), curreq->proxy->dst_port, - - curreq->proxy->id); - curreq->proxy_listener->send(curreq->proxy_listener, curreq); - return 0; - } /* else the packet was not proxied */ - - /* - * Someone's still working on it, so we - * ignore the duplicate request. - */ - radlog(L_ERR, "Discarding duplicate request from " - "client %s port %d - ID: %d due to unfinished request %d", - client->shortname, - packet->src_port, packet->id, - curreq->number); - return 0; - } /* else the authentication vectors were different */ - - /* - * We're waiting for a proxy reply, but no one is - * currently processing the request. We can - * discard the old request, and ignore any reply - * from the home server, because the NAS will - * never care... - */ - if (curreq->proxy && !curreq->proxy_reply && - (curreq->child_pid == NO_SUCH_CHILD_PID)) { - radlog(L_ERR, "Discarding old proxied request %d from " - "client %s port %d - ID: %d due to new request from the client", - curreq->number, client->shortname, - packet->src_port, packet->id); - } else { - /* - * The authentication vectors are different, so - * the NAS has given up on us, as we've taken too - * long to process the request. This is a - * SERIOUS problem! - */ - RAD_SNMP_TYPE_INC(listener, total_packets_dropped); - - radlog(L_ERR, "Dropping conflicting packet from " - "client %s port %d - ID: %d due to unfinished request %d", - client->shortname, - packet->src_port, packet->id, - curreq->number); - return 0; - } - - /* - * The old request is finished. We now check the - * authentication vectors. If the client has sent us a - * request with identical code && ID, but different - * vector, then they MUST have gotten our response, so we - * can delete the original request, and process the new - * one. - * - * If the vectors are the same, then it's a duplicate - * request, and we can send a duplicate reply. - */ - } else if (memcmp(curreq->packet->vector, packet->vector, - sizeof(packet->vector)) == 0) { - RAD_SNMP_INC(rad_snmp.auth.total_dup_requests); - - /* - * If the packet has been delayed, then silently - * send a response, and clear the delayed flag. - * - * Note that this means if the NAS kicks us while - * we're delaying a reject, then the reject may - * be sent sooner than otherwise. - * - * This COULD be construed as a bug. Maybe what - * we want to do is to ignore the duplicate - * packet, and send the reject later. - */ - if (curreq->options & RAD_REQUEST_OPTION_DELAYED_REJECT) { - curreq->options &= ~RAD_REQUEST_OPTION_DELAYED_REJECT; - rad_assert(curreq->listener == listener); - listener->send(listener, curreq); - return 0; - } - - /* - * Maybe we've saved a reply packet. If so, - * re-send it. Otherwise, just complain. - */ - if (curreq->reply->code != 0) { - DEBUG2("Sending duplicate reply " - "to client %s port %d - ID: %d", - client->shortname, - packet->src_port, packet->id); - rad_assert(curreq->listener == listener); - listener->send(listener, curreq); - return 0; - } - - /* - * Else we never sent a reply to the NAS, - * as we decided somehow we didn't like the request. - * - * This shouldn't happen, in general... - */ - DEBUG2("Discarding duplicate request from client %s port %d - ID: %d", - client->shortname, packet->src_port, packet->id); - return 0; - } /* else the vectors were different, so we discard the old request. */ - - /* - * 'packet' has the same source IP, source port, code, - * and Id as 'curreq', but a different authentication - * vector. We can therefore delete 'curreq', as we were - * only keeping it around to send out duplicate replies, - * if the first reply got lost in the network. - */ - if (curreq) { - rl_yank(listener->rl, curreq); - request_free(&curreq); - } - - /* - * A unique per-request counter. - */ - curreq = request_alloc(); /* never fails */ - - if ((curreq->reply = rad_alloc(0)) == NULL) { - radlog(L_ERR, "No memory"); - exit(1); - } - curreq->listener = listener; - curreq->packet = packet; - curreq->packet->timestamp = curreq->timestamp; - curreq->number = request_num_counter++; - strlcpy(curreq->secret, client->secret, sizeof(curreq->secret)); - - /* - * Remember the request in the list. - */ - if (!rl_add(listener->rl, curreq)) { - radlog(L_ERR, "Failed to insert request %d in the list of live requests: discarding", curreq->number); - request_free(&curreq); - return 0; - } - - /* - * The request passes many of our sanity checks. - * From here on in, if anything goes wrong, we - * send a reject message, instead of dropping the - * packet. - */ - - /* - * Build the reply template from the request. - */ - - curreq->reply->sockfd = curreq->packet->sockfd; - curreq->reply->dst_ipaddr = curreq->packet->src_ipaddr; - curreq->reply->src_ipaddr = curreq->packet->dst_ipaddr; - curreq->reply->dst_port = curreq->packet->src_port; - curreq->reply->src_port = curreq->packet->dst_port; - curreq->reply->id = curreq->packet->id; - curreq->reply->code = 0; /* UNKNOWN code */ - memcpy(curreq->reply->vector, curreq->packet->vector, - sizeof(curreq->reply->vector)); - curreq->reply->vps = NULL; - curreq->reply->data = NULL; - curreq->reply->data_len = 0; - - *prequest = curreq; - return 1; -} - - static int socket_print(rad_listen_t *this, char *buffer, size_t bufsize) { size_t len; @@ -580,32 +320,6 @@ static int auth_socket_send(rad_listen_t *listener, REQUEST *request) rad_assert(request->listener == listener); rad_assert(listener->send == auth_socket_send); - /* - * Ensure that the reply is sane - */ - if (request->reply->code == 0) { - DEBUG2("There was no response configured: rejecting request %d", request->number); - request->reply->code = PW_AUTHENTICATION_REJECT; - } - - /* - * If we're delaying authentication rejects, then - * mark the request as delayed, and do NOT send a - * response right now. - * - * However, if it's already marked as delayed, then - * send it now. - */ - if ((request->reply->code == PW_AUTHENTICATION_REJECT) && - ((request->options & RAD_REQUEST_OPTION_DELAYED_REJECT) == 0) && - (mainconfig.reject_delay > 0) && - ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) == 0)) { - DEBUG2("Delaying request %d for %d seconds", - request->number, mainconfig.reject_delay); - request->options |= RAD_REQUEST_OPTION_DELAYED_REJECT; - return 0; - } - return rad_send(request->reply, request->packet, request->secret); } @@ -645,7 +359,8 @@ static int proxy_socket_send(rad_listen_t *listener, REQUEST *request) request->proxy->src_ipaddr = sock->ipaddr; request->proxy->src_port = sock->port; - return rad_send(request->proxy, request->packet, request->proxysecret); + return rad_send(request->proxy, request->packet, + request->home_server->secret); } @@ -714,7 +429,7 @@ static int auth_socket_recv(rad_listen_t *listener, break; } /* switch over packet types */ - if (!common_checks(listener, packet, prequest, client)) { + if (!received_request(listener, packet, prequest, client)) { rad_free(&packet); return 0; } @@ -787,7 +502,7 @@ static int acct_socket_recv(rad_listen_t *listener, * FIXME: Accounting duplicates should be handled * differently than authentication duplicates. */ - if (!common_checks(listener, packet, prequest, client)) { + if (!received_request(listener, packet, prequest, client)) { rad_free(&packet); return 0; } @@ -803,8 +518,7 @@ static int acct_socket_recv(rad_listen_t *listener, static int proxy_socket_recv(rad_listen_t *listener, RAD_REQUEST_FUNP *pfun, REQUEST **prequest) { - REALM *cl; - REQUEST *oldreq; + REQUEST *request; RADIUS_PACKET *packet; RAD_REQUEST_FUNP fun = NULL; char buffer[128]; @@ -816,27 +530,6 @@ static int proxy_socket_recv(rad_listen_t *listener, } /* - * Unsupported stuff - */ - if (packet->src_ipaddr.af != AF_INET) { - rad_assert("PROXY IPV6 NOT SUPPORTED" == NULL); - } - - /* - * FIXME: Add support for home servers! - */ - if ((cl = realm_findbyaddr(packet->src_ipaddr.ipaddr.ip4addr.s_addr, - packet->src_port)) == NULL) { - radlog(L_ERR, "Ignoring request from unknown home server %s port %d", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - buffer, sizeof(buffer)), - packet->src_port); - rad_free(&packet); - return 0; - } - - /* * FIXME: Client MIB updates? */ switch(packet->code) { @@ -863,132 +556,60 @@ static int proxy_socket_recv(rad_listen_t *listener, return 0; } - /* - * Find the original request in the request list - */ - oldreq = rl_find_proxy(packet); - - /* - * If we haven't found the original request which was - * sent, to get this reply. Complain, and discard this - * request, as there's no way for us to send it to a NAS. - */ - if (!oldreq) { - radlog(L_PROXY, "No outstanding request was found for proxy reply from home server %s port %d - ID %d", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - buffer, sizeof(buffer)), - packet->src_port, packet->id); - rad_free(&packet); + request = received_proxy_response(packet); + if (!request) { return 0; } - /* - * The proxy reply has arrived too late, as the original - * (old) request has timed out, been rejected, and marked - * as finished. The client has already received a - * response, so there is nothing that can be done. Delete - * the tardy reply from the home server, and return nothing. - */ - if ((oldreq->reply->code != 0) || - (oldreq->finished)) { - radlog(L_ERR, "Reply from home server %s port %d - ID: %d arrived too late for request %d. Try increasing 'retry_delay' or 'max_request_time'", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - buffer, sizeof(buffer)), - packet->src_port, packet->id, - oldreq->number); - rad_free(&packet); - return 0; - } + rad_assert(fun != NULL); + *pfun = fun; + *prequest = request; - /* - * If there is already a reply, maybe this one is a - * duplicate? - */ - if (oldreq->proxy_reply) { - if (memcmp(oldreq->proxy_reply->vector, - packet->vector, - sizeof(oldreq->proxy_reply->vector)) == 0) { - radlog(L_ERR, "Discarding duplicate reply from home server %s port %d - ID: %d for request %d", - inet_ntop(packet->src_ipaddr.af, - &packet->src_ipaddr.ipaddr, - buffer, sizeof(buffer)), - packet->src_port, packet->id, - oldreq->number); - } else { - /* - * ? The home server gave us a new proxy - * reply, which doesn't match the old - * one. Delete it. - */ - DEBUG2("Ignoring conflicting proxy reply"); - } + return 1; +} - /* - * We've already received a reply, so - * we discard this one, as we don't want - * to do duplicate work. - */ - rad_free(&packet); - return 0; - } /* else there wasn't a proxy reply yet, so we can process it */ - /* - * Refresh the old request, and update it with the proxy - * reply. - * - * ? Can we delete the proxy request here? * Is there - * any more need for it? - * - * FIXME: we probably shouldn't be updating the time - * stamp here. - */ - oldreq->timestamp = time_now; - oldreq->proxy_reply = packet; +static int client_socket_encode(rad_listen_t *listener, REQUEST *request) +{ + if (!request->reply->code) return 0; - /* - * FIXME: we should really verify the digest here, - * before marking this packet as a valid response. - * - * This is a security problem, I think... - */ + rad_encode(request->reply, request->packet, request->secret); + rad_sign(request->reply, request->packet, request->secret); - /* - * Now that we've verified the packet IS actually from - * that home server, and not forged, we can go mark the - * entries for this home server as active. - * - * If we had done this check in the 'find realm by IP address' - * function, then an attacker could force us to use a home - * server which was inactive, by forging reply packets - * which didn't match any request. We would think that - * the reply meant the home server was active, would - * re-activate the realms, and THEN bounce the packet - * as garbage. - */ - for (cl = mainconfig.realms; cl != NULL; cl = cl->next) { - if (oldreq->proxy_reply->src_ipaddr.af != cl->ipaddr.af) continue; - if (cl->ipaddr.af != AF_INET) continue; /* FIXME */ - - if (oldreq->proxy_reply->src_ipaddr.ipaddr.ip4addr.s_addr == cl->ipaddr.ipaddr.ip4addr.s_addr) { - if (oldreq->proxy_reply->src_port == cl->auth_port) { - cl->active = TRUE; - cl->last_reply = oldreq->timestamp; - } else if (oldreq->proxy_reply->src_port == cl->acct_port) { - cl->acct_active = TRUE; - cl->last_reply = oldreq->timestamp; - } - } + return 0; +} + + +static int client_socket_decode(rad_listen_t *listener, REQUEST *request) +{ + if (rad_verify(request->packet, NULL, request->secret) < 0) { + return -1; } - rad_assert(fun != NULL); - *pfun = fun; - *prequest = oldreq; + return rad_decode(request->packet, NULL, request->secret); +} - return 1; +static int proxy_socket_encode(rad_listen_t *listener, REQUEST *request) +{ + rad_encode(request->proxy, NULL, request->home_server->secret); + rad_sign(request->proxy, NULL, request->home_server->secret); + + return 0; } + +static int proxy_socket_decode(rad_listen_t *listener, REQUEST *request) +{ + if (rad_verify(request->proxy_reply, request->proxy, + request->home_server->secret) < 0) { + return -1; + } + + return rad_decode(request->proxy_reply, request->proxy, + request->home_server->secret); +} + + #define STATE_UNOPENED (0) #define STATE_UNLOCKED (1) #define STATE_HEADER (2) @@ -1164,27 +785,6 @@ static int detail_recv(rad_listen_t *listener, } /* - * If we keep track of the outstanding requests, do so - * here. Note that to minimize potential work, we do - * so only once the file is opened & locked. - */ - if (data->max_outstanding) { - int i; - - for (i = 0; i < data->max_outstanding; i++) { - if (!data->outstanding[i]) { - free_slot = i; - break; - } - } - - /* - * All of the slots are full, don't read data. - */ - if (free_slot < 0) return 0; - } - - /* * Catch an out of memory condition which will most likely * never be met. */ @@ -1363,6 +963,34 @@ static int detail_recv(rad_listen_t *listener, * problem. */ alloc_packet: + /* + * If we keep track of the outstanding requests, do so + * here. Note that to minimize potential work, we do + * so only once the file is opened & locked. + */ + if (data->max_outstanding) { + int i; + + for (i = 0; i < data->max_outstanding; i++) { + if (!data->outstanding[i]) { + free_slot = i; + break; + } + } + + /* + * All of the slots are full. Put the LF back, + * so select will say that data is ready, and set + * the state back to reading. Then, return so + * that we don't overload the server. + */ + if (free_slot < 0) { + ungetc('\n', data->fp); + data->state = STATE_READING; + return 0; + } + } + packet = rad_alloc(1); if (!packet) { return 0; /* maybe memory will magically free up... */ @@ -1428,15 +1056,14 @@ static int detail_recv(rad_listen_t *listener, * However, we don't want to keep track of used/unused * id's and ports, as that's a lot of work. This hack * ensures that (if we have real random numbers), that - * there will be a collision on every (2^(16+16+2+24))/2 - * packets, on average. That means we can read 2^32 (4G) + * there will be a collision on every 2^(16+15+15+24 - 1) + * packets, on average. That means we can read 2^37 * packets before having a collision, which means it's - * effectively impossible. Having 4G packets currently - * being process is ridiculous. + * effectively impossible. */ - packet->id = lrad_rand() & 0xff; - packet->src_port = lrad_rand() & 0xffff; - packet->dst_port = lrad_rand() & 0xffff; + packet->id = lrad_rand() & 0xffff; + packet->src_port = 1024 + (lrad_rand() & 0x7fff); + packet->dst_port = 1024 + (lrad_rand() & 0x7fff); packet->dst_ipaddr.af = AF_INET; packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl((INADDR_LOOPBACK & ~0xffffff) | (lrad_rand() & 0xffffff)); @@ -1450,19 +1077,11 @@ static int detail_recv(rad_listen_t *listener, data->state = STATE_HEADER; /* - * FIXME: many of these checks may not be necessary... - */ - if (!common_checks(listener, packet, prequest, &detail_client)) { - rad_free(&packet); - return 0; - } - - /* * Keep track of free slots, as a hack, in an otherwise * unused 'int' */ (*prequest)->simul_max = free_slot; - if (free_slot) data->outstanding[free_slot] = 1; + data->outstanding[free_slot] = 1; *pfun = rad_accounting; @@ -1475,6 +1094,15 @@ static int detail_recv(rad_listen_t *listener, } } + /* + * FIXME: many of these checks may not be necessary when + * reading from the detail file. + */ + if (!received_request(listener, packet, prequest, &detail_client)) { + rad_free(&packet); + return 0; + } + return 1; } @@ -1500,6 +1128,22 @@ static int detail_print(rad_listen_t *this, char *buffer, size_t bufsize) ((listen_detail_t *)(this->data))->detail); } +static int detail_encode(rad_listen_t *this, REQUEST *request) +{ + /* + * We never encode responses "sent to" the detail file. + */ + return 0; +} + +static int detail_decode(rad_listen_t *this, REQUEST *request) +{ + /* + * We never decode responses read from the detail file. + */ + return 0; +} + static const CONF_PARSER detail_config[] = { { "detail", PW_TYPE_STRING_PTR, @@ -1550,48 +1194,32 @@ static int detail_parse(const char *filename, int lineno, return 0; } - -/* - * See radiusd.c & request_list.c - */ -#define SLEEP_FOREVER (65536) -/* - * A generic "update the request list once a second" function. - */ -static int generic_update(rad_listen_t *this, time_t now) -{ - if (!this->rl) return SLEEP_FOREVER; - - return rl_clean_list(this->rl, now); -} - - - static const rad_listen_master_t master_listen[RAD_LISTEN_MAX] = { - { NULL, NULL, NULL, NULL, NULL, NULL}, /* RAD_LISTEN_NONE */ + { NULL, NULL, NULL, NULL, NULL, NULL, NULL}, /* RAD_LISTEN_NONE */ /* authentication */ { common_socket_parse, NULL, auth_socket_recv, auth_socket_send, - generic_update, socket_print }, + socket_print, client_socket_encode, client_socket_decode }, /* accounting */ { common_socket_parse, NULL, acct_socket_recv, acct_socket_send, - generic_update, socket_print}, + socket_print, client_socket_encode, client_socket_decode}, /* proxying */ { NULL, NULL, proxy_socket_recv, proxy_socket_send, - generic_update, socket_print }, /* FIXME: update func is wrong! */ + socket_print, proxy_socket_encode, proxy_socket_decode }, /* detail */ { detail_parse, detail_free, detail_recv, detail_send, - generic_update, detail_print } + detail_print, detail_encode, detail_decode } }; + /* * Binds a listener to a socket. */ @@ -1656,10 +1284,8 @@ static int listen_bind(rad_listen_t *this) } if (equal) { - this->rl = (*last)->rl; this->fd = (*last)->fd; (*last)->fd = -1; - (*last)->rl = NULL; return 0; } } @@ -1672,6 +1298,21 @@ static int listen_bind(rad_listen_t *this) return -1; } +#if 0 +#ifdef O_NONBLOCK + if ((flags = fcntl(this->fd, F_GETFL, NULL)) < 0) { + radlog(L_ERR, "Failure in fcntl: %s)\n", strerror(errno)); + return -1; + } + + flags |= O_NONBLOCK; + if( fcntl(this->fd, F_SETFL, flags) < 0) { + radlog(L_ERR, "Failure in fcntl: %s)\n", strerror(errno)); + return -1; + } +#endif +#endif + return 0; } @@ -1689,8 +1330,9 @@ static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type) this->type = type; this->recv = master_listen[this->type].recv; this->send = master_listen[this->type].send; - this->update = master_listen[this->type].update; this->print = master_listen[this->type].print; + this->encode = master_listen[this->type].encode; + this->decode = master_listen[this->type].decode; switch (type) { case RAD_LISTEN_AUTH: @@ -2070,20 +1712,6 @@ int listen_init(const char *filename, rad_listen_t **head) */ rcode = 0; for (this = *head; this != NULL; this = this->next) { - if ((this->type != RAD_LISTEN_PROXY) && - !this->rl) { - /* - * FIXME: Pass type to rl_init, so that - * it knows how to deal with accounting - * packets. i.e. it caches them, but - * doesn't bother trying to re-transmit. - */ - this->rl = rl_init(); - if (!this->rl) { - rad_assert(0 == 1); /* FIXME: */ - } - } - if (((this->type == RAD_LISTEN_ACCT) && (rcode == RAD_LISTEN_DETAIL)) || ((this->type == RAD_LISTEN_DETAIL) && @@ -2118,8 +1746,6 @@ void listen_free(rad_listen_t **head) free(this->identity); - rl_deinit(this->rl); - /* * Other code may have eaten the FD. */ diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c index 34d9628..bc882c2 100644 --- a/src/main/mainconfig.c +++ b/src/main/mainconfig.c @@ -40,7 +40,6 @@ RCSID("$Id$") #include #include #include -#include #include #include @@ -611,250 +610,6 @@ static int switch_users(void) } -/* - * Create the linked list of realms from the new configuration type - * This way we don't have to change to much in the other source-files - */ -static int generate_realms(const char *filename) -{ - CONF_SECTION *cs; - REALM *my_realms = NULL; - REALM *c, **tail; - char *s, *t, *authhost, *accthost; - const char *name2; - - tail = &my_realms; - for (cs = cf_subsection_find_next(mainconfig.config, NULL, "realm"); - cs != NULL; - cs = cf_subsection_find_next(mainconfig.config, cs, "realm")) { - name2 = cf_section_name2(cs); - if (!name2) { - radlog(L_CONS|L_ERR, "%s[%d]: Missing realm name", - filename, cf_section_lineno(cs)); - return -1; - } - /* - * We've found a realm, allocate space for it - */ - c = rad_malloc(sizeof(REALM)); - memset(c, 0, sizeof(REALM)); - - c->secret[0] = '\0'; - - /* - * No authhost means LOCAL. - */ - if ((authhost = cf_section_value_find(cs, "authhost")) == NULL) { - c->ipaddr.af = AF_INET; - c->ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE); - c->auth_port = 0; - } else { - if ((s = strchr(authhost, ':')) != NULL) { - *s++ = 0; - c->auth_port = atoi(s); - } else { - c->auth_port = PW_AUTH_UDP_PORT; - } - if (strcmp(authhost, "LOCAL") == 0) { - /* - * Local realms don't have an IP address, - * secret, or port. - */ - c->ipaddr.af = AF_INET; - c->ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE); - c->auth_port = 0; - } else { - if (ip_hton(authhost, AF_INET, - &c->ipaddr) < 0) { - radlog(L_ERR, "%s[%d]: Host %s not found", - filename, cf_section_lineno(cs), - authhost); - return -1; - } - } - - /* - * Double check length, just to be sure! - */ - if (strlen(authhost) >= sizeof(c->server)) { - radlog(L_ERR, "%s[%d]: Server name of length %u is greater than allowed: %u", - filename, cf_section_lineno(cs), - strlen(authhost), - sizeof(c->server) - 1); - return -1; - } - } - - /* - * No accthost means LOCAL - */ - if ((accthost = cf_section_value_find(cs, "accthost")) == NULL) { - c->acct_ipaddr.af = AF_INET; - c->acct_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE); - c->acct_port = 0; - } else { - if ((s = strchr(accthost, ':')) != NULL) { - *s++ = 0; - c->acct_port = atoi(s); - } else { - c->acct_port = PW_ACCT_UDP_PORT; - } - if (strcmp(accthost, "LOCAL") == 0) { - /* - * Local realms don't have an IP address, - * secret, or port. - */ - c->acct_ipaddr.af = AF_INET; - c->acct_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE); - c->acct_port = 0; - } else { - if (ip_hton(accthost, AF_INET, - &c->acct_ipaddr) < 0) { - radlog(L_ERR, "%s[%d]: Host %s not found", - filename, cf_section_lineno(cs), - accthost); - return -1; - } - } - - if (strlen(accthost) >= sizeof(c->acct_server)) { - radlog(L_ERR, "%s[%d]: Server name of length %u is greater than allowed: %u", - filename, cf_section_lineno(cs), - strlen(accthost), - sizeof(c->acct_server) - 1); - return -1; - } - } - - if (strlen(name2) >= sizeof(c->realm)) { - radlog(L_ERR, "%s[%d]: Realm name of length %u is greater than allowed %u", - filename, cf_section_lineno(cs), - strlen(name2), - sizeof(c->server) - 1); - return -1; - } - - strcpy(c->realm, name2); - if (authhost) strcpy(c->server, authhost); - if (accthost) strcpy(c->acct_server, accthost); - - /* - * If one or the other of authentication/accounting - * servers is set to LOCALHOST, then don't require - * a shared secret. - */ - rad_assert(c->ipaddr.af == AF_INET); - rad_assert(c->acct_ipaddr.af == AF_INET); - if ((c->ipaddr.ipaddr.ip4addr.s_addr != htonl(INADDR_NONE)) || - (c->acct_ipaddr.ipaddr.ip4addr.s_addr != htonl(INADDR_NONE))) { - if ((s = cf_section_value_find(cs, "secret")) == NULL ) { - radlog(L_ERR, "%s[%d]: No shared secret supplied for realm: %s", - filename, cf_section_lineno(cs), name2); - return -1; - } - - if (strlen(s) >= sizeof(c->secret)) { - radlog(L_ERR, "%s[%d]: Secret of length %u is greater than the allowed maximum of %u.", - filename, cf_section_lineno(cs), - strlen(s), sizeof(c->secret) - 1); - return -1; - } - strlcpy((char *)c->secret, s, sizeof(c->secret)); - } - - c->striprealm = 1; - - if ((cf_section_value_find(cs, "nostrip")) != NULL) - c->striprealm = 0; - if ((cf_section_value_find(cs, "noacct")) != NULL) - c->acct_port = 0; - if ((cf_section_value_find(cs, "trusted")) != NULL) - c->trusted = 1; - if ((cf_section_value_find(cs, "notrealm")) != NULL) - c->notrealm = 1; - if ((cf_section_value_find(cs, "notsuffix")) != NULL) - c->notrealm = 1; - if ((t = cf_section_value_find(cs,"ldflag")) != NULL) { - static const LRAD_NAME_NUMBER ldflags[] = { - { "fail_over", 0 }, - { "round_robin", 1 }, - { NULL, 0 } - }; - - c->ldflag = lrad_str2int(ldflags, t, -1); - if (c->ldflag == -1) { - radlog(L_ERR, "%s[%d]: Unknown value \"%s\" for ldflag", - filename, cf_section_lineno(cs), - t); - return -1; - } - - } else { - c->ldflag = 0; /* non, make it fail-over */ - } - c->active = TRUE; - c->acct_active = TRUE; - - c->next = NULL; - *tail = c; - tail = &c->next; - } - - /* - * And make these realms preferred over the ones - * in the 'realms' file. - */ - *tail = mainconfig.realms; - mainconfig.realms = my_realms; - - /* - * Ensure that all of the flags agree for the realms. - * - * Yeah, it's O(N^2), but it's only once, and the - * maximum number of realms is small. - */ - for(c = mainconfig.realms; c != NULL; c = c->next) { - REALM *this; - - /* - * Check that we cannot load balance to LOCAL - * realms, as that doesn't make any sense. - */ - rad_assert(c->ipaddr.af == AF_INET); - rad_assert(c->acct_ipaddr.af == AF_INET); - if ((c->ldflag == 1) && - ((c->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)) || - (c->acct_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)))) { - radlog(L_ERR | L_CONS, "ERROR: Realm %s cannot be load balanced to LOCAL", - c->realm); - exit(1); - } - - /* - * Compare this realm to all others, to ensure - * that the configuration is consistent. - */ - for (this = c->next; this != NULL; this = this->next) { - if (strcasecmp(c->realm, this->realm) != 0) { - continue; - } - - /* - * Same realm: Different load balancing - * flag: die. - */ - if (c->ldflag != this->ldflag) { - radlog(L_ERR | L_CONS, "ERROR: Inconsistent value in realm %s for load balancing 'ldflag' attribute", - c->realm); - exit(1); - } - } - } - - return 0; -} - - static const LRAD_NAME_NUMBER str2dest[] = { { "null", RADLOG_NULL }, { "files", RADLOG_FILES }, @@ -1023,22 +778,9 @@ int read_mainconfig(int reload) mainconfig.config = cs; cf_section_free(&oldcs); - /* - * Old-style realms file. - */ - snprintf(buffer, sizeof(buffer), "%.200s/%.50s", radius_dir, RADIUS_REALMS); - DEBUG2("read_config_files: reading realms"); - if (read_realms_file(buffer) < 0) { - radlog(L_ERR|L_CONS, "Errors reading realms"); - return -1; - } - - /* - * If there isn't any realms it isn't fatal.. - */ snprintf(buffer, sizeof(buffer), "%.200s/%.50s", radius_dir, mainconfig.radiusd_conf); - if (generate_realms(buffer) < 0) { + if (!realms_init(buffer)) { return -1; } @@ -1152,8 +894,6 @@ int read_mainconfig(int reload) clients_free(old_clients); } - rl_init_proxy(); - /* Reload the modules. */ DEBUG2("radiusd: entering modules setup"); if (setup_modules(reload) < 0) { @@ -1174,7 +914,7 @@ int free_mainconfig(void) */ cf_section_free(&mainconfig.config); free(mainconfig.radiusd_conf); - realm_free(mainconfig.realms); + realms_free(); listen_free(&mainconfig.listen); xlat_free(); dict_free(); diff --git a/src/main/modcall.c b/src/main/modcall.c index 3dcd7a4..c5a3b83 100644 --- a/src/main/modcall.c +++ b/src/main/modcall.c @@ -328,7 +328,7 @@ int modcall(int component, modcallable *c, REQUEST *request) * A module has taken too long to process the request, * and we've been told to stop processing it. */ - if (request->options & RAD_REQUEST_OPTION_STOP_NOW) { + if (request->master_state == REQUEST_STOP_PROCESSING) { myresult = RLM_MODULE_FAIL; break; } diff --git a/src/main/modules.c b/src/main/modules.c index 7324b81..25971af 100644 --- a/src/main/modules.c +++ b/src/main/modules.c @@ -724,6 +724,7 @@ int setup_modules(int reload) do_component[RLM_COMPONENT_POST_PROXY] = 1; break; + default: rad_assert(0 == 1); break; @@ -948,20 +949,6 @@ int setup_modules(int reload) */ int module_authorize(int autz_type, REQUEST *request) { - /* - * Older versions of the server would pass proxy requests - * through the 'authorize' sections twice; once when the - * packet was received from the NAS, and again after the - * reply was received from the home server. Now that we - * have a 'post_proxy' section, the replies from the home - * server should be sent through that, instead of through - * the 'authorize' section again. - */ - if (request->proxy != NULL) { - DEBUG2(" authorize: Skipping authorize in post-proxy stage"); - return RLM_MODULE_NOOP; - } - return indexed_modcall(RLM_COMPONENT_AUTZ, autz_type, request); } diff --git a/src/main/proxy.c b/src/main/proxy.c deleted file mode 100644 index 4610906..0000000 --- a/src/main/proxy.c +++ /dev/null @@ -1,565 +0,0 @@ -/* - * proxy.c Proxy stuff. - * - * Version: $Id$ - * - * 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 - * - * Copyright 2000,2006 The FreeRADIUS server project - * Copyright 2000 Miquel van Smoorenburg - * Copyright 2000 Chris Parker - */ - -#include -RCSID("$Id$") - -#include - -#include - -#ifdef HAVE_NETINET_IN_H -# include -#endif - -#include -#include -#include -#include - -#include -#include -#include -#include - -/* - * We received a response from a remote radius server. - * Call the post-proxy modules. - */ -int proxy_receive(REQUEST *request) -{ - int rcode; - int post_proxy_type = 0; - VALUE_PAIR *vp; - - /* - * Delete any reply we had accumulated until now. - */ - pairfree(&request->reply->vps); - - /* - * Run the packet through the post-proxy stage, - * BEFORE playing games with the attributes. - */ - vp = pairfind(request->config_items, PW_POST_PROXY_TYPE); - if (vp) { - DEBUG2(" Found Post-Proxy-Type %s", vp->vp_strvalue); - post_proxy_type = vp->lvalue; - } - rcode = module_post_proxy(post_proxy_type, request); - - /* - * Delete the Proxy-State Attributes from the reply. - * These include Proxy-State attributes from us and - * remote server. - */ - pairdelete(&request->proxy_reply->vps, PW_PROXY_STATE); - - /* - * Add the attributes left in the proxy reply to - * the reply list. - */ - pairadd(&request->reply->vps, request->proxy_reply->vps); - request->proxy_reply->vps = NULL; - - /* - * Free proxy request pairs. - */ - pairfree(&request->proxy->vps); - - /* - * FIXME: If the packet is an Access-Challenge, - * THEN add it to a cache, which does: - * - * (src IP, State) -> (home server ip/port) - * - * This allows the load-balancing code to - * work for EAP... - * - * Alternately, we can delete the State from the home - * server, and use our own.. that might be better. - */ - - return rcode; -} - -/* - * Add a proxy-pair to the end of the request. - */ -static void proxy_addinfo(REQUEST *request) -{ - VALUE_PAIR *proxy_pair; - - proxy_pair = paircreate(PW_PROXY_STATE, PW_TYPE_STRING); - if (proxy_pair == NULL) { - radlog(L_ERR|L_CONS, "no memory"); - exit(1); - } - sprintf(proxy_pair->vp_strvalue, "%d", request->packet->id); - proxy_pair->length = strlen(proxy_pair->vp_strvalue); - - pairadd(&request->proxy->vps, proxy_pair); -} - - -/* - * Like realm find, but does load balancing, and we don't - * wake up any sleeping realms. Someone should already have - * done that. - * - * It also does NOT do fail-over to default if the realms are dead, - * as that decision has already been made. - */ -static REALM *proxy_realm_ldb(REQUEST *request, const char *realm_name, - int accounting) -{ - REALM *cl, *lb; - uint32_t count; - - /* - * FIXME: If the packet contains a State attribute, - * AND the realm is load-balance, - * AND there is a matching - * State attribute in the cached entry, THEN proxy it to - * that realm. - */ - - lb = NULL; - count = 0; - for (cl = mainconfig.realms; cl; cl = cl->next) { - /* - * Wake up any sleeping realm. - * - * Note that the 'realm find' function will only - * wake up the FIRST realm which matches. We've - * got to wake up ALL of the matching realms. - */ - if (cl->wakeup <= request->timestamp) { - cl->active = TRUE; - } - if (cl->acct_wakeup <= request->timestamp) { - cl->acct_active = TRUE; - } - - /* - * Asked for auth/acct, and the auth/acct server - * is not active. Skip it. - */ - if ((!accounting && !cl->active) || - (accounting && !cl->acct_active)) { - continue; - } - - /* - * The realm name doesn't match, skip it. - */ - if (strcasecmp(cl->realm, realm_name) != 0) { - continue; - } - - /* - * Fail-over, pick the first one that matches. - */ - if ((count == 0) && /* if size > 0, we have round-robin */ - (cl->ldflag == 0)) { - return cl; - } - - /* - * We're doing load-balancing. Pick a random - * number, which will be used to determine which - * home server is chosen. - */ - if (!lb) { - lb = cl; - count = 1; - continue; - } - - /* - * Keep track of how many load balancing servers - * we've gone through. - */ - count++; - - /* - * See the "camel book" for why this works. - * - * If (rand(0..n) < 1), pick the current realm. - * We add a scale factor of 65536, to avoid - * floating point. - */ - if ((count * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) { - lb = cl; - } - } /* loop over the realms */ - - /* - * Return the load-balanced realm. - */ - return lb; -} - -/* - * Relay the request to a remote server. - * Returns: - * - * RLM_MODULE_FAIL: we don't reply, caller returns without replying - * RLM_MODULE_NOOP: caller falls through to normal processing - * RLM_MODULE_HANDLED : we reply, caller returns without replying - */ -int proxy_send(REQUEST *request) -{ - int rcode; - int pre_proxy_type = 0; - VALUE_PAIR *realmpair; - VALUE_PAIR *strippedname; - VALUE_PAIR *vp; - REALM *realm; - char *realmname; - - /* - * Not authentication or accounting. Stop it. - */ - if ((request->packet->code != PW_AUTHENTICATION_REQUEST) && - (request->packet->code != PW_ACCOUNTING_REQUEST)) { - DEBUG2(" ERROR: Cannot proxy packets of type %d", - request->packet->code); - return RLM_MODULE_FAIL; - } - - /* - * The timestamp is used below to figure the - * next_try. The request needs to "hang around" until - * either the other server sends a reply or the retry - * count has been exceeded. Until then, it should not - * be eligible for the time-based cleanup. --Pac. */ - - realmpair = pairfind(request->config_items, PW_PROXY_TO_REALM); - if (!realmpair) { - /* - * Not proxying, so we can exit from the proxy - * code. - */ - return RLM_MODULE_NOOP; - } - - /* - * If the server has already decided to reject the request, - * then don't try to proxy it. - */ - if (request->reply->code == PW_AUTHENTICATION_REJECT) { - DEBUG2("Cancelling proxy as request was already rejected"); - return RLM_MODULE_REJECT; - } - if (((vp = pairfind(request->config_items, PW_AUTH_TYPE)) != NULL) && - (vp->lvalue == PW_AUTHTYPE_REJECT)) { - DEBUG2("Cancelling proxy as request was already rejected"); - return RLM_MODULE_REJECT; - } - /* - * Length == 0 means it exists, but there's no realm. - * Don't proxy it. - */ - if (realmpair->length == 0) { - return RLM_MODULE_NOOP; - } - - realmname = (char *)realmpair->vp_strvalue; - - /* - * Look for the realm, using the load balancing - * version of realm find. - */ - realm = proxy_realm_ldb(request, realmname, - (request->packet->code == PW_ACCOUNTING_REQUEST)); - if (realm == NULL) { - DEBUG2(" ERROR: Failed to find live home server for realm %s", - realmname); - return RLM_MODULE_FAIL; - } - - /* - * Remember that we sent the request to a Realm. - */ - pairadd(&request->packet->vps, - pairmake("Realm", realm->realm, T_OP_EQ)); - - /* - * Access-Request: look for LOCAL realm. - * Accounting-Request: look for LOCAL realm. - */ - if (((request->packet->code == PW_AUTHENTICATION_REQUEST) && - (realm->ipaddr.af == AF_INET) && - (realm->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE))) || - ((request->packet->code == PW_ACCOUNTING_REQUEST) && - (realm->acct_ipaddr.af == AF_INET) && - (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)))) { - DEBUG2(" WARNING: Cancelling proxy to Realm %s, as the realm is local.", - realm->realm); - return RLM_MODULE_NOOP; - } - - /* - * This is mainly for radrelay. Don't proxy packets back - * to servers which sent them to us. - */ - if ((request->packet->code == PW_ACCOUNTING_REQUEST) && - (request->listener->type == RAD_LISTEN_DETAIL) && - (realm->acct_ipaddr.af == AF_INET) && - (request->packet->src_ipaddr.af == AF_INET) && - (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == request->packet->src_ipaddr.ipaddr.ip4addr.s_addr)) { - DEBUG2(" rlm_realm: Packet came from realm %s, proxy cancelled", realm->realm); - return RLM_MODULE_NOOP; - } - - /* - * Allocate the proxy packet, only if it wasn't already - * allocated by a module. This check is mainly to support - * the proxying of EAP-TTLS and EAP-PEAP tunneled requests. - * - * In those cases, the EAP module creates a "fake" - * request, and recursively passes it through the - * authentication stage of the server. The module then - * checks if the request was supposed to be proxied, and - * if so, creates a proxy packet from the TUNNELED request, - * and not from the EAP request outside of the tunnel. - * - * The proxy then works like normal, except that the response - * packet is "eaten" by the EAP module, and encapsulated into - * an EAP packet. - */ - if (!request->proxy) { - /* - * Now build a new RADIUS_PACKET. - * - * FIXME: it could be that the id wraps around - * too fast if we have a lot of requests, it - * might be better to keep a seperate ID value - * per remote server. - * - * OTOH the remote radius server should be smart - * enough to compare _both_ ID and vector. - * Right? - */ - if ((request->proxy = rad_alloc(TRUE)) == NULL) { - radlog(L_ERR|L_CONS, "no memory"); - exit(1); - } - - /* - * We now massage the attributes to be proxied... - */ - - /* - * Copy the request, then look up name and - * plain-text password in the copy. - * - * Note that the User-Name attribute is the - * *original* as sent over by the client. The - * Stripped-User-Name attribute is the one hacked - * through the 'hints' file. - */ - request->proxy->vps = paircopy(request->packet->vps); - } - - /* - * Strip the name, if told to. - * - * Doing it here catches the case of proxied tunneled - * requests. - */ - if (realm->striprealm == TRUE && - (strippedname = pairfind(request->proxy->vps, PW_STRIPPED_USER_NAME)) != NULL) { - /* - * If there's a Stripped-User-Name attribute in - * the request, then use THAT as the User-Name - * for the proxied request, instead of the - * original name. - * - * This is done by making a copy of the - * Stripped-User-Name attribute, turning it into - * a User-Name attribute, deleting the - * Stripped-User-Name and User-Name attributes - * from the vps list, and making the new - * User-Name the head of the vps list. - */ - vp = pairfind(request->proxy->vps, PW_USER_NAME); - if (!vp) { - vp = paircreate(PW_USER_NAME, PW_TYPE_STRING); - if (!vp) { - radlog(L_ERR|L_CONS, "no memory"); - exit(1); - } - vp->next = request->proxy->vps; - request->proxy->vps = vp; - } - memcpy(vp->vp_strvalue, strippedname->vp_strvalue, - sizeof(vp->vp_strvalue)); - vp->length = strippedname->length; - - /* - * Do NOT delete Stripped-User-Name. - */ - } - - /* - * If there is no PW_CHAP_CHALLENGE attribute but - * there is a PW_CHAP_PASSWORD we need to add it - * since we can't use the request authenticator - * anymore - we changed it. - */ - if (pairfind(request->proxy->vps, PW_CHAP_PASSWORD) && - pairfind(request->proxy->vps, PW_CHAP_CHALLENGE) == NULL) { - vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING); - if (!vp) { - radlog(L_ERR|L_CONS, "no memory"); - exit(1); - } - vp->length = AUTH_VECTOR_LEN; - memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN); - pairadd(&(request->proxy->vps), vp); - } - - request->proxy->code = request->packet->code; - if (request->packet->code == PW_AUTHENTICATION_REQUEST) { - request->proxy->dst_port = realm->auth_port; - request->proxy->dst_ipaddr = realm->ipaddr; - } else if (request->packet->code == PW_ACCOUNTING_REQUEST) { - request->proxy->dst_port = realm->acct_port; - request->proxy->dst_ipaddr = realm->acct_ipaddr; - } - - /* - * Add PROXY_STATE attribute, before pre-proxy stage, - * so the pre-proxy modules have access to it. - * - * Note that, at this point, the proxied request HAS NOT - * been assigned a RADIUS Id. - */ - proxy_addinfo(request); - - /* - * Set up for sending the request. - */ - memcpy(request->proxysecret, realm->secret, sizeof(request->proxysecret)); - request->proxy_try_count = mainconfig.proxy_retry_count - 1; - - vp = NULL; - if (request->packet->code == PW_ACCOUNTING_REQUEST) { - vp = pairfind(request->proxy->vps, PW_ACCT_DELAY_TIME); - } - if (vp) { - request->proxy->timestamp = request->timestamp - vp->lvalue; - } else { - request->proxy->timestamp = request->timestamp; - } - request->proxy_start_time = request->timestamp; - - /* - * Do pre-proxying. - */ - vp = pairfind(request->config_items, PW_PRE_PROXY_TYPE); - if (vp) { - DEBUG2(" Found Pre-Proxy-Type %s", vp->vp_strvalue); - pre_proxy_type = vp->lvalue; - } - rcode = module_pre_proxy(pre_proxy_type, request); - switch (rcode) { - /* - * Only proxy the packet if the pre-proxy code succeeded. - */ - case RLM_MODULE_NOOP: - case RLM_MODULE_OK: - case RLM_MODULE_UPDATED: - /* - * Delay sending the proxy packet until after we've - * done the work above, playing with the request. - * - * After this point, it becomes dangerous to play with - * the request data structure, as the reply MAY come in - * and get processed before we're done with it here. - */ - request->options |= RAD_REQUEST_OPTION_PROXIED; - - /* - * If it's a fake request, don't send the proxy - * packet. The outer tunnel session will take - * care of doing that. - */ - if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) == 0) { - /* - * Add the proxied request to the - * list of outstanding proxied - * requests, BEFORE we send it, so - * we have fewer problems with race - * conditions when the responses come - * back very quickly. - */ - if (!rl_add_proxy(request)) { - DEBUG("ERROR: Failed to proxy request %d", - request->number); - return RLM_MODULE_FAIL; /* caller doesn't reply */ - } - - /* - * We're still running, encode & sign the - * packet outside of the critical section. - */ - if (request->child_pid != NO_SUCH_CHILD_PID) { - rad_encode(request->proxy, NULL, - (char *)request->proxysecret); - rad_sign(request->proxy, NULL, - (char *)request->proxysecret); - } else { - request->proxy_listener->send(request->proxy_listener, - request); - } - } - rcode = RLM_MODULE_HANDLED; /* caller doesn't reply */ - break; - /* - * The module handled the request, don't reply. - */ - case RLM_MODULE_HANDLED: - break; - /* - * Neither proxy, nor reply to invalid requests. - */ - case RLM_MODULE_FAIL: - case RLM_MODULE_INVALID: - case RLM_MODULE_NOTFOUND: - case RLM_MODULE_REJECT: - case RLM_MODULE_USERLOCK: - default: - rcode = RLM_MODULE_FAIL; /* caller doesn't reply */ - break; - } - - /* - * Do NOT free request->proxy->vps, the pairs are needed - * for the retries! - */ - return rcode; -} diff --git a/src/main/radiusd.c b/src/main/radiusd.c index ce19e46..b6d623c 100644 --- a/src/main/radiusd.c +++ b/src/main/radiusd.c @@ -68,11 +68,8 @@ RCSID("$Id$") #include #include #include -#include #include -#define SLEEP_FOREVER (65536) - /* * Global variables. */ @@ -117,11 +114,10 @@ int main(int argc, char *argv[]) int pid; int max_fd; int status; - int sleep_time = SLEEP_FOREVER; int spawn_flag = TRUE; int dont_fork = FALSE; int sig_hup_block = FALSE; - time_t last_cleaned_lists = 0; + struct timeval tv, *ptv = NULL; #ifdef HAVE_SIGACTION struct sigaction act; @@ -397,7 +393,7 @@ int main(int argc, char *argv[]) * It's called the thread pool, but it does a little * more than that. */ - thread_pool_init(spawn_flag); + radius_event_init(spawn_flag); /* * Use linebuffered or unbuffered stdout if @@ -521,6 +517,8 @@ int main(int argc, char *argv[]) */ detach_modules(); + radius_event_free(); + free(radius_dir); /* @@ -588,18 +586,18 @@ int main(int argc, char *argv[]) } #endif - if (sleep_time == SLEEP_FOREVER) { + if (!ptv) { DEBUG2("Nothing to do. Sleeping until we see a request."); - status = select(max_fd + 1, &readfds, NULL, NULL, NULL); - } else { - struct timeval tv; - - DEBUG2("Waking up in %d seconds...", sleep_time); - - tv.tv_sec = sleep_time; - tv.tv_usec = 0; - status = select(max_fd + 1, &readfds, NULL, NULL, &tv); + } else if (tv.tv_sec) { +#if 0 + DEBUG2("Waking up in %d.%06d seconds...", + (int) tv.tv_sec, (int) tv.tv_usec); +#else + DEBUG2("Waking up in %d seconds...", + (int) tv.tv_sec); +#endif } + status = select(max_fd + 1, &readfds, NULL, NULL, ptv); if (status == -1) { /* * On interrupts, we clean up the request @@ -662,25 +660,25 @@ int main(int argc, char *argv[]) } /* - * Do per-socket receive processing of the - * packet. + * Do per-socket receive processing of + * the packet. This also takes care of + * inserting the request into the event + * tree, and adding it to the queue for + * threads. */ if (!listener->recv(listener, &fun, &request)) { continue; } + // EVENT FIX FIXME! Nuke this! + /* * Drop the request into the thread pool, * and let the thread pool take care of * doing something with it. */ if (!thread_pool_addrequest(request, fun)) { - /* - * FIXME: Maybe just drop - * the packet on the floor? - */ - request_reject(request, REQUEST_FAIL_NO_THREADS); - request->finished = TRUE; + request->child_state = REQUEST_DONE; } } /* loop over listening sockets*/ @@ -711,26 +709,8 @@ int main(int argc, char *argv[]) } #endif - /* - * Loop through the request lists once per - * second, to clean up old requests. - */ - if (last_cleaned_lists != time_now) { - last_cleaned_lists = time_now; - - DEBUG2("--- Walking the entire request list ---"); - sleep_time = SLEEP_FOREVER; - for (listener = mainconfig.listen; - listener != NULL; - listener = listener->next) { - int next; - - next = listener->update(listener, time_now); - if (next < sleep_time) { - sleep_time = next; - } - } - } + ptv = &tv; + radius_event_process(&ptv); #ifdef HAVE_PTHREAD_H diff --git a/src/main/realms.c b/src/main/realms.c new file mode 100644 index 0000000..6ed552a --- /dev/null +++ b/src/main/realms.c @@ -0,0 +1,1129 @@ +/* + * realms.c Realm handling code + * + * Version: $Id$ + * + * 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 + * + * Copyright 2000,2006 The FreeRADIUS server project + * Copyright 2000 Miquel van Smoorenburg + * Copyright 2000 Alan DeKok + */ + +#include +RCSID("$Id$") + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +static rbtree_t *realms_byname = NULL; + +static rbtree_t *home_servers_byaddr = NULL; +static rbtree_t *home_servers_byname = NULL; + +static rbtree_t *home_pools_byname = NULL; + +static int realm_name_cmp(const void *one, const void *two) +{ + const REALM *a = one; + const REALM *b = two; + + return strcasecmp(a->name, b->name); +} + + +static int home_server_name_cmp(const void *one, const void *two) +{ + const home_server *a = one; + const home_server *b = two; + + return strcasecmp(a->name, b->name); +} + +static int home_server_addr_cmp(const void *one, const void *two) +{ + const home_server *a = one; + const home_server *b = two; + + if (a->ipaddr.af < b->ipaddr.af) return -1; + if (a->ipaddr.af > b->ipaddr.af) return +1; + + if (a->port < b->port) return -1; + if (a->port > b->port) return +1; + + switch (a->ipaddr.af) { + case AF_INET: + return memcmp(&a->ipaddr.ipaddr.ip4addr, + &b->ipaddr.ipaddr.ip4addr, + sizeof(a->ipaddr.ipaddr.ip4addr)); + break; + case AF_INET6: + return memcmp(&a->ipaddr.ipaddr.ip6addr, + &b->ipaddr.ipaddr.ip6addr, + sizeof(a->ipaddr.ipaddr.ip6addr)); + break; + default: + break; + } + + return -1; +} + + +static int home_pool_name_cmp(const void *one, const void *two) +{ + const home_pool_t *a = one; + const home_pool_t *b = two; + + return strcasecmp(a->name, b->name); +} + + +void realms_free(void) +{ + rbtree_free(home_servers_byname); + home_servers_byname = NULL; + + rbtree_free(home_servers_byaddr); + home_servers_byaddr = NULL; + + rbtree_free(home_pools_byname); + home_pools_byname = NULL; + + rbtree_free(realms_byname); + realms_byname = NULL; +} + + +int realms_init(const char *filename) +{ + CONF_SECTION *cs; + + if (realms_byname) return 1; + + realms_byname = rbtree_create(realm_name_cmp, free, 0); + if (!realms_byname) { + realms_free(); + return 0; + } + + home_servers_byaddr = rbtree_create(home_server_addr_cmp, free, 0); + if (!home_servers_byaddr) { + realms_free(); + return 0; + } + + home_servers_byname = rbtree_create(home_server_name_cmp, NULL, 0); + if (!home_servers_byname) { + realms_free(); + return 0; + } + + home_pools_byname = rbtree_create(home_pool_name_cmp, free, 0); + if (!home_pools_byname) { + realms_free(); + return 0; + } + + for (cs = cf_subsection_find_next(mainconfig.config, NULL, "realm"); + cs != NULL; + cs = cf_subsection_find_next(mainconfig.config, cs, "realm")) { + if (!realm_add(filename, cs)) { + realms_free(); + return 0; + } + } + + return 1; +} + +static struct in_addr hs_ip4addr; +static struct in6_addr hs_ip6addr; +static char *hs_type = NULL; +static char *hs_check = NULL; + +static CONF_PARSER home_server_config[] = { + { "ipaddr", PW_TYPE_IPADDR, + 0, &hs_ip4addr, NULL }, + { "ipv6addr", PW_TYPE_IPV6ADDR, + 0, &hs_ip6addr, NULL }, + + { "hostname", PW_TYPE_STRING_PTR, + offsetof(home_server,hostname), NULL, NULL}, + { "port", PW_TYPE_INTEGER, + offsetof(home_server,port), NULL, "0" }, + + { "type", PW_TYPE_STRING_PTR, + 0, &hs_type, NULL }, + + { "secret", PW_TYPE_STRING_PTR, + offsetof(home_server,secret), NULL, NULL}, + + { "response_window", PW_TYPE_INTEGER, + offsetof(home_server,response_window), NULL, "30" }, + { "max_outstanding", PW_TYPE_INTEGER, + offsetof(home_server,max_outstanding), NULL, "65536" }, + + { "zombie_period", PW_TYPE_INTEGER, + offsetof(home_server,zombie_period), NULL, "40" }, + { "ping_check", PW_TYPE_STRING_PTR, + 0, &hs_check, "none" }, + + { "ping_interval", PW_TYPE_INTEGER, + offsetof(home_server,ping_interval), NULL, "30" }, + { "num_pings_to_alive", PW_TYPE_INTEGER, + offsetof(home_server,num_pings_to_alive), NULL, "3" }, + { "revive_interval", PW_TYPE_INTEGER, + offsetof(home_server,revive_interval), NULL, "300" }, + + { "username", PW_TYPE_STRING_PTR, + offsetof(home_server,ping_user_name), NULL, NULL}, + { "password", PW_TYPE_STRING_PTR, + offsetof(home_server,ping_user_password), NULL, NULL}, + + { NULL, -1, 0, NULL, NULL } /* end the list */ + +}; + + +static int home_server_add(const char *filename, CONF_SECTION *cs) +{ + const char *name2; + home_server *home; + + name2 = cf_section_name1(cs); + if (!name2 || (strcasecmp(name2, "home_server") != 0)) { + radlog(L_ERR, "%s[%d]: Section is not a home_server.", + filename, cf_section_lineno(cs)); + return 0; + } + + name2 = cf_section_name2(cs); + if (!name2) { + radlog(L_ERR, "%s[%d]: Home server section is missing a name.", + filename, cf_section_lineno(cs)); + return 0; + } + + home = rad_malloc(sizeof(*home)); + memset(home, 0, sizeof(*home)); + + home->name = name2; + + memset(&hs_ip4addr, 0, sizeof(hs_ip4addr)); + memset(&hs_ip6addr, 0, sizeof(hs_ip6addr)); + cf_section_parse(cs, home, home_server_config); + + if (!home->hostname && (htonl(hs_ip4addr.s_addr) == INADDR_NONE) && + IN6_IS_ADDR_UNSPECIFIED(&hs_ip6addr)) { + radlog(L_ERR, "%s[%d]: No hostname, IPv4 address, or IPv6 address defined for home server %s.", + filename, cf_section_lineno(cs), name2); + free(home); + free(hs_type); + hs_type = NULL; + free(hs_check); + hs_check = NULL; + return 0; + } + + /* + * FIXME: Parse home->hostname! + * + * Right now, only ipaddr && ip6addr are used. + * The old-style parsing still allows hostnames. + */ + if (htonl(hs_ip4addr.s_addr) != INADDR_NONE) { + home->ipaddr.af = AF_INET; + home->ipaddr.ipaddr.ip4addr = hs_ip4addr; + + } else if (!IN6_IS_ADDR_UNSPECIFIED(&hs_ip6addr)) { + home->ipaddr.af = AF_INET6; + home->ipaddr.ipaddr.ip6addr = hs_ip6addr; + + } else { + radlog(L_ERR, "%s[%d]: FIXME: parse hostname for home server %s.", + filename, cf_section_lineno(cs), name2); + free(home); + free(hs_type); + hs_type = NULL; + free(hs_check); + hs_check = NULL; + return 0; + } + + if (!home->port || (home->port > 65535)) { + radlog(L_ERR, "%s[%d]: No port, or invalid port defined for home server %s.", + filename, cf_section_lineno(cs), name2); + free(home); + free(hs_type); + hs_type = NULL; + free(hs_check); + hs_check = NULL; + return 0; + } + + if (0) { + radlog(L_ERR, "%s[%d]: Fatal error! Home server %s is ourselves!", + filename, cf_section_lineno(cs), name2); + free(home); + free(hs_type); + hs_type = NULL; + free(hs_check); + hs_check = NULL; + return 0; + } + + if (strcasecmp(hs_type, "auth") == 0) { + home->type = HOME_TYPE_AUTH; + + } else if (strcasecmp(hs_type, "acct") == 0) { + home->type = HOME_TYPE_ACCT; + + } else { + radlog(L_ERR, "%s[%d]: Invalid type \"%s\" for home server %s.", + filename, cf_section_lineno(cs), hs_type, name2); + free(home); + free(hs_type); + hs_type = NULL; + free(hs_check); + hs_check = NULL; + return 0; + } + free(hs_type); + hs_type = NULL; + + if (!home->secret) { + radlog(L_ERR, "%s[%d]: No shared secret defined for home server %s.", + filename, cf_section_lineno(cs), name2); + free(home); + return 0; + } + + if (strcasecmp(hs_check, "none") == 0) { + home->ping_check = HOME_PING_CHECK_NONE; + + } else if (strcasecmp(hs_check, "status-server") == 0) { + home->ping_check = HOME_PING_CHECK_STATUS_SERVER; + + } else if (strcasecmp(hs_check, "request") == 0) { + home->ping_check = HOME_PING_CHECK_REQUEST; + + } else { + radlog(L_ERR, "%s[%d]: Invalid ping_check \"%s\" for home server %s.", + filename, cf_section_lineno(cs), hs_check, name2); + free(home); + free(hs_check); + hs_check = NULL; + return 0; + } + free(hs_check); + hs_check = NULL; + + if ((home->ping_check != HOME_PING_CHECK_NONE) && + (home->ping_check != HOME_PING_CHECK_STATUS_SERVER)) { + if (!home->ping_user_name) { + radlog(L_INFO, "%s[%d]: You must supply a user name to enable ping checks", + filename, cf_section_lineno(cs)); + free(home); + return 0; + } + + if ((home->type == HOME_TYPE_AUTH) && + !home->ping_user_password) { + radlog(L_INFO, "%s[%d]: You must supply a password to enable ping checks", + filename, cf_section_lineno(cs)); + free(home); + return 0; + } + } + + if (rbtree_finddata(home_servers_byaddr, home)) { + radlog(L_INFO, "%s[%d]: Ignoring duplicate home server %s.", + filename, cf_section_lineno(cs), name2); + return 1; + } + + if (!rbtree_insert(home_servers_byname, home)) { + radlog(L_ERR, "%s[%d]: Internal error adding home server %s.", + filename, cf_section_lineno(cs), name2); + free(home); + return 0; + } + + if (!rbtree_insert(home_servers_byaddr, home)) { + rbtree_deletebydata(home_servers_byname, home); + radlog(L_ERR, "%s[%d]: Internal error adding home server %s.", + filename, cf_section_lineno(cs), name2); + free(home); + return 0; + } + + if (home->response_window < 5) home->response_window = 5; + if (home->response_window > 60) home->response_window = 60; + + if (home->max_outstanding < 8) home->max_outstanding = 8; + if (home->max_outstanding > 65536*16) home->max_outstanding = 65536*16; + + if (home->ping_interval < 6) home->ping_interval = 6; + if (home->ping_interval > 120) home->ping_interval = 120; + + if (home->zombie_period < 20) home->zombie_period = 20; + if (home->zombie_period > 120) home->zombie_period = 120; + + if (home->zombie_period < home->response_window) { + home->zombie_period = home->response_window; + } + + if (home->num_pings_to_alive < 3) home->num_pings_to_alive = 3; + if (home->num_pings_to_alive > 10) home->num_pings_to_alive = 10; + + if (home->revive_interval < 60) home->revive_interval = 60; + if (home->revive_interval > 3600) home->revive_interval = 3600; + + return 1; +} + + +static int server_pool_add(const char *filename, CONF_SECTION *cs) +{ + const char *name2; + home_pool_t *pool; + const char *value; + CONF_PAIR *cp; + int num_home_servers; + + name2 = cf_section_name1(cs); + if (!name2 || (strcasecmp(name2, "server_pool") != 0)) { + radlog(L_ERR, "%s[%d]: Section is not a server_pool.", + filename, cf_section_lineno(cs)); + return 0; + } + + name2 = cf_section_name2(cs); + if (!name2) { + radlog(L_ERR, "%s[%d]: Server pool section is missing a name.", + filename, cf_section_lineno(cs)); + return 0; + } + + num_home_servers = 0; + for (cp = cf_pair_find(cs, "home_server"); + cp != NULL; + cp = cf_pair_find_next(cs, cp, "home_server")) { + num_home_servers++; + } + + if (num_home_servers == 0) { + radlog(L_ERR, "%s[%d]: No home servers defined in pool %s", + filename, cf_section_lineno(cs), name2); + return 0; + } + + pool = rad_malloc(sizeof(*pool) + num_home_servers * sizeof(pool->servers[0])); + memset(pool, 0, sizeof(*pool) + num_home_servers * sizeof(pool->servers[0])); + + pool->type = HOME_POOL_FAIL_OVER; + pool->name = name2; + + cp = cf_pair_find(cs, "type"); + if (cp) { + value = cf_pair_value(cp); + if (!value) { + radlog(L_ERR, "%s[%d]: No value given for type.", + filename, cf_pair_lineno(cp)); + free(pool); + return 0; + } + + if (strcmp(value, "load-balance") == 0) { + pool->type = HOME_POOL_LOAD_BALANCE; + + } else if (strcmp(value, "fail-over") == 0) { + pool->type = HOME_POOL_FAIL_OVER; + + } else { + radlog(L_ERR, "%s[%d]: Unknown type \"%s\".", + filename, cf_pair_lineno(cp), value); + free(pool); + return 0; + } + + DEBUG2(" server_pool %s: type = %s", name2, value); + } + + for (cp = cf_pair_find(cs, "home_server"); + cp != NULL; + cp = cf_pair_find_next(cs, cp, "home_server")) { + home_server myhome, *home; + + value = cf_pair_value(cp); + if (!value) { + radlog(L_ERR, "%s[%d]: No value given for home_server.", + filename, cf_pair_lineno(cp)); + free(pool); + return 0; + } + + myhome.name = value; + + home = rbtree_finddata(home_servers_byname, &myhome); + if (!home) { + CONF_SECTION *server_cs; + + server_cs = cf_section_sub_find_name2(NULL, + "home_server", + value); + if (!server_cs) { + radlog(L_ERR, "%s[%d]: Unknown home_server \"%s\".", + filename, cf_pair_lineno(cp), value); + free(pool); + return 0; + } + + if (!home_server_add(filename, server_cs)) { + free(pool); + return 0; + } + + home = rbtree_finddata(home_servers_byname, &myhome); + if (!home) { + rad_assert("Internal sanity check failed"); + return 0; + } + } + + if (!pool->server_type) { + rad_assert(home->type != 0); + pool->server_type = home->type; + + } else if (pool->server_type != home->type) { + radlog(L_ERR, "%s[%d]: Home server \"%s\" is not of the same type as previous servers in server pool %s", + filename, cf_pair_lineno(cp), value, pool->name); + free(pool); + return 0; + } + + if (0) { + DEBUG2("Warning: Duplicate home server %s in server pool %s", home->name, pool->name); + continue; + } + + DEBUG2(" server_pool %s: home_server = %s", name2, home->name); + pool->servers[pool->num_home_servers] = home; + pool->num_home_servers++; + + } /* loop over home_server's */ + + if (!rbtree_insert(home_pools_byname, pool)) { + rad_assert("Internal sanity check failed"); + return 0; + } + + rad_assert(pool->server_type != 0); + + return 1; +} + + +static int old_server_add(const char *filename, int lineno, + const char *name, const char *secret, + home_pool_type_t ldflag, home_pool_t **pool_p, + int type) +{ + int i, insert_point, num_home_servers; + home_server myhome, *home; + home_pool_t mypool, *pool; + CONF_SECTION *cs; + + /* + * LOCAL realms get sanity checked, and nothing else happens. + */ + if (strcmp(name, "LOCAL") == 0) { + if (*pool_p) { + radlog(L_ERR, "%s[%d]: Realm \"%s\" cannot be both LOCAL and remote", filename, lineno, name); + return 0; + } + return 1; + } + + mypool.name = name; + pool = rbtree_finddata(home_pools_byname, &mypool); + if (pool) { + if (pool->type != ldflag) { + radlog(L_ERR, "%s[%d]: Inconsistent ldflag for server pool \"%s\"", filename, lineno, name); + return 0; + } + + if (pool->server_type != type) { + radlog(L_ERR, "%s[%d]: Inconsistent home server type for server pool \"%s\"", filename, lineno, name); + return 0; + } + } + + myhome.name = name; + home = rbtree_finddata(home_servers_byname, &myhome); + if (home) { + if (strcmp(home->secret, secret) != 0) { + radlog(L_ERR, "%s[%d]: Inconsistent shared secret for home server \"%s\"", filename, lineno, name); + return 0; + } + + if (home->type != type) { + radlog(L_ERR, "%s[%d]: Inconsistent type for home server \"%s\"", filename, lineno, name); + return 0; + } + + /* + * See if the home server is already listed + * in the pool. If so, do nothing else. + */ + if (pool) for (i = 0; i < pool->num_home_servers; i++) { + if (pool->servers[i] == home) { + return 1; + } + } + } + + /* + * If we do have a pool, check that there is room to + * insert the home server we've found, or the one that we + * create here. + * + * Note that we insert it into the LAST available + * position, in order to maintain the same order as in + * the configuration files. + */ + insert_point = -1; + if (pool) { + for (i = pool->num_home_servers - 1; i >= 0; i--) { + if (pool->servers[i]) break; + + if (!pool->servers[i]) { + insert_point = i; + } + } + + if (insert_point < 0) { + radlog(L_ERR, "%s[%d]: No room in pool to add home server \"%s\". Please update the realm configuration to use the new-style home servers and server pools.", filename, lineno, name); + return 0; + } + } + + /* + * No home server, allocate one. + */ + if (!home) { + const char *p; + char *q; + + home = rad_malloc(sizeof(*home)); + memset(home, 0, sizeof(*home)); + + home->name = name; + home->hostname = name; + home->type = type; + home->secret = secret; + + p = strchr(name, ':'); + if (!p) { + if (type == HOME_TYPE_AUTH) { + home->port = PW_AUTH_UDP_PORT; + } else { + home->port = PW_ACCT_UDP_PORT; + } + + p = name; + q = NULL; + + } else if (p == name) { + radlog(L_ERR, "%s[%d]: Invalid hostname %s.", + filename, lineno, name); + free(home); + return 0; + + } else { + home->port = atoi(p + 1); + if ((home->port == 0) || (home->port > 65535)) { + radlog(L_ERR, "%s[%d]: Invalid port %s.", + filename, lineno, p + 1); + free(home); + return 0; + } + + q = rad_malloc((p - name) + 1); + memcpy(q, name, (p - name)); + q[p - name] = '\0'; + p = q; + } + + if (ip_hton(p, AF_UNSPEC, &home->ipaddr) < 0) { + radlog(L_ERR, "%s[%d]: Failed looking up hostname %s.", + filename, lineno, p); + free(home); + free(q); + return 0; + } + free(q); + + /* + * Use the old-style configuration. + */ + home->max_outstanding = 65535*16; + home->zombie_period = mainconfig.proxy_retry_delay * mainconfig.proxy_retry_count; + if (home->zombie_period == 0) home->zombie_period =30; + home->response_window = home->zombie_period - 1; + + home->ping_check = HOME_PING_CHECK_NONE; + + home->revive_interval = mainconfig.proxy_dead_time; + + if (rbtree_finddata(home_servers_byaddr, home)) { + radlog(L_ERR, "%s[%d]: Home server %s has the same IP address as another home server.", + filename, lineno, name); + free(home); + return 0; + } + + if (!rbtree_insert(home_servers_byname, home)) { + radlog(L_ERR, "%s[%d]: Internal error adding home server %s.", + filename, lineno, name); + free(home); + return 0; + } + + if (!rbtree_insert(home_servers_byaddr, home)) { + rbtree_deletebydata(home_servers_byname, home); + radlog(L_ERR, "%s[%d]: Internal error adding home server %s.", + filename, lineno, name); + free(home); + return 0; + } + } + + /* + * We now have a home server, see if we can insert it + * into pre-existing pool. + */ + if (insert_point >= 0) { + rad_assert(pool != NULL); + pool->servers[insert_point] = home; + return 1; + } + + rad_assert(pool == NULL); + rad_assert(home != NULL); + + /* + * Count the old-style realms of this name. + */ + num_home_servers = 0; + for (cs = cf_section_sub_find_name2(mainconfig.config, "realm", name); + cs != NULL; + cs = cf_section_sub_find_name2(cs, "realm", name)) { + num_home_servers++; + } + + + pool = rad_malloc(sizeof(*pool) + num_home_servers * sizeof(pool->servers[0])); + memset(pool, 0, sizeof(*pool) + num_home_servers * sizeof(pool->servers[0])); + + pool->name = name; + pool->type = ldflag; + pool->server_type = type; + pool->num_home_servers = num_home_servers; + pool->servers[0] = home; + + if (!rbtree_insert(home_pools_byname, pool)) { + rad_assert("Internal sanity check failed"); + return 0; + } + + *pool_p = pool; + + return 1; +} + +static int old_realm_config(const char *filename, CONF_SECTION *cs, REALM *r) +{ + char *host; + const char *secret; + home_pool_type_t ldflag; + + secret = cf_section_value_find(cs, "secret"); + + host = cf_section_value_find(cs, "ldflag"); + if (!host || + (strcasecmp(host, "fail_over") == 0)) { + ldflag = HOME_POOL_FAIL_OVER; + DEBUG2(" realm %s: ldflag = fail_over", r->name); + + } else if (strcasecmp(host, "round_robin") == 0) { + ldflag = HOME_POOL_LOAD_BALANCE; + DEBUG2(" realm %s: ldflag = round_robin", r->name); + + } else { + radlog(L_ERR, "%s[%d]: Unknown value \"%s\" for ldflag", + filename, cf_section_lineno(cs), host); + return 0; + } + + /* + * Allow old-style if it doesn't exist, or if it exists and + * it's LOCAL. + */ + if (((host = cf_section_value_find(cs, "authhost")) != NULL) && + (strcmp(host, "LOCAL") != 0)) { + if (!secret) { + radlog(L_ERR, "%s[%d]: No shared secret supplied for realm: %s", + filename, cf_section_lineno(cs), r->name); + return 0; + } + + DEBUG2(" realm %s: authhost = %s", r->name, host); + + if (!old_server_add(filename, cf_section_lineno(cs), + host, secret, ldflag, + &r->auth_pool, HOME_TYPE_AUTH)) { + return 0; + } + } + + if (((host = cf_section_value_find(cs, "accthost")) != NULL) && + (strcmp(host, "LOCAL") != 0)) { + if (!secret) { + radlog(L_ERR, "%s[%d]: No shared secret supplied for realm: %s", + filename, cf_section_lineno(cs), r->name); + return 0; + } + + DEBUG2(" realm %s: accthost = %s", r->name, host); + + if (!old_server_add(filename, cf_section_lineno(cs), + host, secret, ldflag, + &r->auth_pool, HOME_TYPE_ACCT)) { + return 0; + } + } + + if (secret) DEBUG2(" realm %s: secret = %s", r->name, secret); + + return 1; + +} + + +static int add_pool_to_realm(const char *filename, int lineno, + const char *name, home_pool_t **dest, + int server_type) +{ + home_pool_t mypool, *pool; + + mypool.name = name; + pool = rbtree_finddata(home_pools_byname, &mypool); + if (!pool) { + CONF_SECTION *pool_cs; + + pool_cs = cf_section_sub_find_name2(NULL, "server_pool", + name); + if (!pool_cs) { + radlog(L_ERR, "%s[%d]: Failed to find server_pool \"%s\"", + filename, lineno, name); + return 0; + } + + if (!server_pool_add(filename, pool_cs)) { + return 0; + } + + pool = rbtree_finddata(home_pools_byname, &mypool); + if (!pool) { + rad_assert("Internal sanity check failed"); + return 0; + } + } + + if (pool->server_type != server_type) { + radlog(L_ERR, "%s[%d]: Incompatible server_pool \"%s\" (mixed auth_pool / acct_pool)", + filename, lineno, name); + return 0; + } + + *dest = pool; + + return 1; +} + +int realm_add(const char *filename, CONF_SECTION *cs) +{ + const char *name2; + char *pool = NULL; + REALM *r; + CONF_PAIR *cp; + + name2 = cf_section_name1(cs); + if (!name2 || (strcasecmp(name2, "realm") != 0)) { + radlog(L_ERR, "%s[%d]: Section is not a realm.", + filename, cf_section_lineno(cs)); + return 0; + } + + name2 = cf_section_name2(cs); + if (!name2) { + radlog(L_ERR, "%s[%d]: Realm section is missing the realm name.", + filename, cf_section_lineno(cs)); + return 0; + } + + /* + * The realm MAY already exist if it's an old-style realm. + * In that case, merge the old-style realm with this one. + */ + r = realm_find(name2); + if (r) { + if (cf_pair_find(cs, "auth_pool") || + cf_pair_find(cs, "acct_pool")) { + radlog(L_ERR, "%s[%d]: Duplicate realm \"%s\"", + filename, cf_section_lineno(cs), name2); + return 0; + } + + if (!old_realm_config(filename, cs, r)) { + return 0; + } + + return 1; + } + + r = rad_malloc(sizeof(*r)); + memset(r, 0, sizeof(*r)); + + r->name = name2; + + /* + * Prefer new configuration to old one. + */ + cp = cf_pair_find(cs, "auth_pool"); + if (cp) pool = cf_pair_value(cp); + if (cp && pool) { + if (!add_pool_to_realm(filename, cf_pair_lineno(cp), + pool, &r->auth_pool, HOME_TYPE_AUTH)) { + free(r); + return 0; + } + DEBUG2(" realm %s: auth_pool = %s", name2, pool); + } + + cp = cf_pair_find(cs, "acct_pool"); + if (cp) pool = cf_pair_value(cp); + if (cp && pool) { + if (!add_pool_to_realm(filename, cf_pair_lineno(cp), + pool, &r->acct_pool, HOME_TYPE_ACCT)) { + free(r); + return 0; + } + DEBUG2(" realm %s: acct_pool = %s", name2, pool); + } + + r->striprealm = 1; + + if ((cf_section_value_find(cs, "nostrip")) != NULL) { + r->striprealm = 0; + DEBUG2(" realm %s: nostrip", name2); + } + + /* + * We're a new-style realm. Complain if we see the old + * directives. + */ + if (r->auth_pool || r->acct_pool) { + if (((cp = cf_pair_find(cs, "authhost")) != NULL) || + ((cp = cf_pair_find(cs, "accthost")) != NULL) || + ((cp = cf_pair_find(cs, "secret")) != NULL) || + ((cp = cf_pair_find(cs, "ldflag")) != NULL)) { + DEBUG2("WARNING: Ignoring old-style configuration entry \"%s\" in realm \"%s\"", cf_pair_attr(cp), r->name); + } + + + /* + * The realm MAY be an old-style realm, as there + * was no auth_pool or acct_pool. Double-check + * it, just to be safe. + */ + } else if (!old_realm_config(filename, cs, r)) { + free(r); + return 0; + } + + if (!rbtree_insert(realms_byname, r)) { + rad_assert("Internal sanity check failed"); + free(r); + return 0; + } + + return 1; +} + + +/* + * Find a realm in the REALM list. + */ +REALM *realm_find(const char *name) +{ + REALM myrealm; + + if (!name) name = "NULL"; + + myrealm.name = name; + return rbtree_finddata(realms_byname, &myrealm); +} + + +home_server *home_server_ldb(REALM *r, int code) +{ + uint32_t count; + home_server *lb; + home_pool_t *pool; + + if (code == PW_AUTHENTICATION_REQUEST) { + pool = r->auth_pool; + + } else if (code == PW_ACCOUNTING_REQUEST) { + pool = r->acct_pool; + + } else { + rad_assert("Internal sanity check failed"); + return NULL; + } + + if (!pool) { + rad_assert("Internal sanity check failed"); + return NULL; + } + + lb = NULL; + + for (count = 0; count < pool->num_home_servers; count++) { + home_server *home = pool->servers[count]; + + if (home->state == HOME_STATE_IS_DEAD) { + continue; + } + + /* + * This home server is too busy. Choose another one. + */ + if (home->currently_outstanding >= home->max_outstanding) { + continue; + } + + /* + * Fail-over, pick the first one that matches. + */ + if (pool->type == HOME_POOL_FAIL_OVER) { + return home; + } + + /* + * FUTURE: If we're well into "zombie_period", + * then we probably want to think about + * load-balancing the packet somewhere else, as + * the home server is probably dead. + */ + + /* + * We're doing load-balancing. Pick a random + * number, which will be used to determine which + * home server is chosen. + */ + if (!lb) { + lb = home; + continue; + } + + /* + * See the "camel book" for why this works. + * + * If (rand(0..n) < 1), pick the current server. + * We add a scale factor of 65536, to avoid + * floating point. + */ + if (((count + 1) * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) { + lb = home; + } + } /* loop over the realms */ + + /* + * No live match found, and no fallback to the "DEFAULT" + * realm. We fix this by blindly marking all servers as + * "live". But only do it for ones that don't support + * "pings", as they will be marked live when they + * actually are live. + */ + if (!lb && + !mainconfig.proxy_fallback && + mainconfig.wake_all_if_all_dead) { + for (count = 0; count < pool->num_home_servers; count++) { + home_server *home = pool->servers[count]; + + if ((home->state == HOME_STATE_IS_DEAD) && + (home->ping_check == HOME_PING_CHECK_NONE)) { + home->state = HOME_STATE_ALIVE; + if (!lb) lb = home; + } + } + } + + /* + * Still nothing. Look up the DEFAULT realm, but only + * if we weren't looking up the NULL or DEFAULT realms. + */ + if (!lb && + mainconfig.proxy_fallback && + (strcmp(r->name, "NULL") != 0) && + (strcmp(r->name, "DEFAULT") != 0)) { + REALM *rd = realm_find("DEFAULT"); + + if (rd) { + DEBUG2(" Realm %s has no live home servers. Falling back to the DEFAULT realm.", r->name); + return home_server_ldb(rd, code); + } + } + + /* + * Return the appropriate home server. + */ + return lb; +} + + +home_server *home_server_find(lrad_ipaddr_t *ipaddr, int port) +{ + home_server myhome; + + myhome.ipaddr = *ipaddr; + myhome.port = port; + + return rbtree_finddata(home_servers_byaddr, &myhome); +} diff --git a/src/main/request_list.c b/src/main/request_list.c deleted file mode 100644 index 0173ea6..0000000 --- a/src/main/request_list.c +++ /dev/null @@ -1,703 +0,0 @@ -/* - * request_list.c Hide the handling of the REQUEST list from - * the main server. - * - * Version: $Id$ - * - * 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 - * - * Copyright 2003-2004,2006 The FreeRADIUS server project - */ - -#include -RCSID("$Id$") - -#include - -#include -#include -#include - -#include -#include -#include -#include - -struct request_list_t { - lrad_packet_list_t *pl; -}; - -#ifdef HAVE_PTHREAD_H -static pthread_mutex_t proxy_mutex; -#else -/* - * This is easier than ifdef's throughout the code. - */ -#define pthread_mutex_lock(_x) -#define pthread_mutex_unlock(_x) -#endif - -static lrad_packet_list_t *proxy_list = NULL; - -/* - * We keep the proxy FD's here. The RADIUS Id's are marked - * "allocated" per Id, via a bit per proxy FD. - */ -static int proxy_fds[32]; -static rad_listen_t *proxy_listeners[32]; - -/* - * Initialize the request list. - */ -request_list_t *rl_init(void) -{ - request_list_t *rl = rad_malloc(sizeof(*rl)); - - /* - * Initialize the request_list[] array. - */ - memset(rl, 0, sizeof(*rl)); - - rl->pl = lrad_packet_list_create(0); - if (!rl->pl) { - rad_assert("FAIL" == NULL); - } - - return rl; -} - -int rl_init_proxy(void) -{ - /* - * Hacks, so that multiple users can call rl_init, - * and it won't get excited. - * - * FIXME: Move proxy stuff to another struct entirely. - */ - if (proxy_list) return 0; - - /* - * Create the tree for managing proxied requests and - * responses. - */ - proxy_list = lrad_packet_list_create(1); - if (!proxy_list) { - rad_assert("FAIL" == NULL); - } - -#ifdef HAVE_PTHREAD_H - /* - * For now, always create the mutex. - * - * Later, we can only create it if there are multiple threads. - */ - if (pthread_mutex_init(&proxy_mutex, NULL) != 0) { - radlog(L_ERR, "FATAL: Failed to initialize proxy mutex: %s", - strerror(errno)); - exit(1); - } -#endif - - { - int i; - rad_listen_t *listener; - - /* - * Mark the Fd's as unused. - */ - for (i = 0; i < 32; i++) proxy_fds[i] = -1; - - for (listener = mainconfig.listen; - listener != NULL; - listener = listener->next) { - if (listener->type == RAD_LISTEN_PROXY) { - /* - * FIXME: This works only because we - * start off with one proxy socket. - */ - proxy_fds[listener->fd & 0x1f] = listener->fd; - proxy_listeners[listener->fd & 0x1f] = listener; - lrad_packet_list_socket_add(proxy_list, listener->fd); - break; - } - } - } - - return 1; -} - -static int rl_free_entry(void *ctx, void *data) -{ - ctx = ctx; /* -Wunused */ - REQUEST *request = lrad_packet2myptr(REQUEST, packet, data); - -#ifdef HAVE_PTHREAD_H - /* - * If someone is processing this request, kill - * them, and mark the request as not being used. - * - * FIXME: Move the request to the "dead pool", - * and don't kill the thread. - */ - if (request->child_pid != NO_SUCH_CHILD_PID) { - pthread_kill(request->child_pid, SIGKILL); - request->child_pid = NO_SUCH_CHILD_PID; - } -#endif - request_free(&request); - - return 0; -} - - -/* - * Delete everything in the request list. - * - * This should be called only when debugging the server... - */ -void rl_deinit(request_list_t *rl) -{ - if (!rl) return; - - if (proxy_list) { - lrad_packet_list_free(proxy_list); - proxy_list = NULL; - } - - /* - * Delete everything in the table, too. - */ - lrad_packet_list_walk(rl->pl, NULL, rl_free_entry); - - lrad_packet_list_free(rl->pl); - - /* - * Just to ensure no one is using the memory. - */ - memset(rl, 0, sizeof(*rl)); - free(rl); -} - - -/* - * Yank a request from the tree, without free'ing it. - */ -void rl_yank(request_list_t *rl, REQUEST *request) -{ -#ifdef WITH_SNMP - /* - * Update the SNMP statistics. - * - * Note that we do NOT do this in rad_respond(), - * as that function is called from child threads. - * Instead, we update the stats when a request is - * deleted, because only the main server thread calls - * this function... - */ - if (mainconfig.do_snmp) { - switch (request->reply->code) { - case PW_AUTHENTICATION_ACK: - rad_snmp.auth.total_responses++; - rad_snmp.auth.total_access_accepts++; - break; - - case PW_AUTHENTICATION_REJECT: - rad_snmp.auth.total_responses++; - rad_snmp.auth.total_access_rejects++; - break; - - case PW_ACCESS_CHALLENGE: - rad_snmp.auth.total_responses++; - rad_snmp.auth.total_access_challenges++; - break; - - case PW_ACCOUNTING_RESPONSE: - rad_snmp.acct.total_responses++; - break; - - default: - break; - } - } -#endif - - /* - * Delete the request from the list. - */ - lrad_packet_list_yank(rl->pl, request->packet); - - /* - * If there's a proxied packet, and we're still - * waiting for a reply, then delete the packet - * from the list of outstanding proxied requests. - */ - if (request->proxy && - (request->proxy_outstanding > 0)) { - pthread_mutex_lock(&proxy_mutex); - lrad_packet_list_yank(proxy_list, request->proxy); - lrad_packet_list_id_free(proxy_list, request->proxy); - pthread_mutex_unlock(&proxy_mutex); - } -} - - -/* - * Delete a request from the tree. - */ -void rl_delete(request_list_t *rl, REQUEST *request) -{ - rl_yank(rl, request); - request_free(&request); -} - - -/* - * Add a request to the request list. - */ -int rl_add(request_list_t *rl, REQUEST *request) -{ - return lrad_packet_list_insert(rl->pl, &request->packet); -} - -/* - * Look up a particular request, using: - * - * Request ID, request code, source IP, source port, - * - * Note that we do NOT use the request vector to look up requests. - * - * We MUST NOT have two requests with identical (id/code/IP/port), and - * different vectors. This is a serious error! - */ -REQUEST *rl_find(request_list_t *rl, RADIUS_PACKET *packet) -{ - RADIUS_PACKET **packet_p; - - packet_p = lrad_packet_list_find(rl->pl, packet); - if (!packet_p) return NULL; - - return lrad_packet2myptr(REQUEST, packet, packet_p); -} - -/* - * Add an entry to the proxy tree. - * - * This is the ONLY function in this source file which may be called - * from a child thread. It therefore needs mutexes... - */ -int rl_add_proxy(REQUEST *request) -{ - int i, proxy; - char buf[128]; - - request->proxy_outstanding = 1; - request->proxy->sockfd = -1; - - pthread_mutex_lock(&proxy_mutex); - - if (!lrad_packet_list_id_alloc(proxy_list, request->proxy)) { - int found; - rad_listen_t *proxy_listener; - - /* - * Allocate a new proxy Fd. This function adds it - * into the list of listeners. - */ - proxy_listener = proxy_new_listener(); - if (!proxy_listener) { - pthread_mutex_unlock(&proxy_mutex); - DEBUG2("ERROR: Failed to create a new socket for proxying requests."); - return 0; - } - - /* - * Cache it locally. - */ - found = -1; - proxy = proxy_listener->fd; - for (i = 0; i < 32; i++) { - /* - * Found a free entry. Save the socket, - * and remember where we saved it. - */ - if (proxy_fds[(proxy + i) & 0x1f] == -1) { - found = (proxy + i) & 0x1f; - proxy_fds[found] = proxy; - proxy_listeners[found] = proxy_listener; - break; - } - } - rad_assert(found >= 0); - - if (!lrad_packet_list_socket_add(proxy_list, proxy_listener->fd)) { - pthread_mutex_unlock(&proxy_mutex); - DEBUG2("ERROR: Failed to create a new socket for proxying requests."); - return 0; /* leak proxy_listener */ - - } - - if (!lrad_packet_list_id_alloc(proxy_list, request->proxy)) { - pthread_mutex_unlock(&proxy_mutex); - DEBUG2("ERROR: Failed to create a new socket for proxying requests."); - return 0; - } - } - - /* - * FIXME: Hack until we get rid of rad_listen_t, and put - * the information into the packet_list. - */ - proxy = -1; - for (i = 0; i < 32; i++) { - if (proxy_fds[i] == request->proxy->sockfd) { - proxy = i; - break; - } - } - rad_assert(proxy >= 0); - - rad_assert(proxy_fds[proxy] != -1); - request->proxy_listener = proxy_listeners[proxy]; - - if (!lrad_packet_list_insert(proxy_list, &request->proxy)) { - pthread_mutex_unlock(&proxy_mutex); - DEBUG2("ERROR: Failed to insert entry into proxy list"); - return 0; - } - - pthread_mutex_unlock(&proxy_mutex); - - DEBUG3(" proxy: allocating destination %s port %d - Id %d", - inet_ntop(request->proxy->dst_ipaddr.af, - &request->proxy->dst_ipaddr.ipaddr, buf, sizeof(buf)), - request->proxy->dst_port, - request->proxy->id); - - return 1; -} - - -/* - * Look up a particular request, using: - * - * Request Id, request code, source IP, source port, - * - * Note that we do NOT use the request vector to look up requests. - * - * We MUST NOT have two requests with identical (id/code/IP/port), and - * different vectors. This is a serious error! - */ -REQUEST *rl_find_proxy(RADIUS_PACKET *reply) -{ - RADIUS_PACKET **proxy_p; - REQUEST *request; - - pthread_mutex_lock(&proxy_mutex); - proxy_p = lrad_packet_list_find_byreply(proxy_list, reply); - - if (!proxy_p) { - pthread_mutex_unlock(&proxy_mutex); - return NULL; - } - - request = lrad_packet2myptr(REQUEST, proxy, proxy_p); - rad_assert(request->proxy_outstanding > 0); - request->proxy_outstanding--; - - /* - * Received all of the replies we expect. - * delete it from the managed list. - */ - if (request->proxy_outstanding == 0) { - lrad_packet_list_yank(proxy_list, request->proxy); - lrad_packet_list_id_free(proxy_list, request->proxy); - } - pthread_mutex_unlock(&proxy_mutex); - - return request; -} - - -/* - * Return the number of requests in the request list. - */ -int rl_num_requests(request_list_t *rl) -{ - return lrad_packet_list_num_elements(rl->pl); -} - - -/* - * See also radiusd.c - */ -#define SLEEP_FOREVER (65536) -typedef struct rl_walk_t { - time_t now; - int sleep_time; - request_list_t *rl; -} rl_walk_t; - - -/* - * Refresh a request, by using cleanup_delay, max_request_time, etc. - * - * When walking over the request list, all of the per-request - * magic is done here. - */ -static int refresh_request(void *ctx, void *data) -{ - int time_passed; - rl_walk_t *info = (rl_walk_t *) ctx; - child_pid_t child_pid; - request_list_t *rl = info->rl; - REQUEST *request = lrad_packet2myptr(REQUEST, packet, data); - - rad_assert(request->magic == REQUEST_MAGIC); - - time_passed = (int) (info->now - request->timestamp); - - /* - * If the request is marked as a delayed reject, AND it's - * time to send the reject, then do so now. - */ - if (request->finished && - ((request->options & RAD_REQUEST_OPTION_DELAYED_REJECT) != 0)) { - rad_assert(request->child_pid == NO_SUCH_CHILD_PID); - if (time_passed < mainconfig.reject_delay) { - goto reject_delay; - } - - reject_packet: - /* - * Clear the 'delayed reject' bit, so that we - * don't do this again, and fall through to - * setting cleanup delay. - */ - request->listener->send(request->listener, request); - request->options &= ~RAD_REQUEST_OPTION_DELAYED_REJECT; - - /* - * FIXME: Beware interaction with cleanup_delay, - * where we might send a reject, and immediately - * there-after clean it up! - */ - } - - /* - * If the request is finished, THEN - * check that more than cleanup_delay seconds have passed - * since it was received - * OR, if this is a request which had the "don't cache" - * option set, then it CANNOT have a duplicate - * SO, clean it up - */ - if (request->finished && - ((time_passed >= mainconfig.cleanup_delay) || - ((request->options & RAD_REQUEST_OPTION_DONT_CACHE) != 0))) { - rad_assert(request->child_pid == NO_SUCH_CHILD_PID); - /* - * Request completed, delete it, and unlink it - * from the currently 'alive' list of requests. - */ - cleanup: - DEBUG2("Cleaning up request %d ID %d with timestamp %08lx", - request->number, request->packet->id, - (unsigned long) request->timestamp); - - /* - * Delete the request. - */ - rl_delete(rl, request); - return 0; - } - - /* - * If more than max_request_time has passed since - * we received the request, kill it. - */ - if (time_passed >= mainconfig.max_request_time) { - int number; - - child_pid = request->child_pid; - number = request->number; - - /* - * There MUST be a RAD_PACKET reply. - */ - rad_assert(request->reply != NULL); - - /* - * If we've tried to proxy the request, and - * the proxy server hasn't responded, then - * we send a REJECT back to the caller. - * - * For safety, we assert that there is no child - * handling the request. If the assertion fails, - * it means that we've sent a proxied request to - * the home server, and the child thread is still - * sitting on the request! - */ - if (request->proxy && !request->proxy_reply) { - rad_assert(request->child_pid == NO_SUCH_CHILD_PID); - - radlog(L_ERR, "Rejecting request %d due to lack of any response from home server %s port %d", - request->number, - client_name_old(&request->packet->src_ipaddr), - request->packet->src_port); - request_reject(request, REQUEST_FAIL_HOME_SERVER); - request->finished = TRUE; - return 0; - } - -#ifdef DELETE_BLOCKED_REQUESTS - /* - * Calling pthread_cancel() without a cancel handler is an - * exceedingly bad idea. This code is left here in case - * we implement per-module cancel handlers later. - * See freeradius-devel archives, - * "cancelling requests due to max_request_time" - * - * If implemented, just remove the #ifdef's for DELETE_BLOCKED_REQUESTS - * scattered throughout the code (this file and others), and - * add the option back to radiusd.conf.in. - */ - if (mainconfig.kill_unresponsive_children) { - if (child_pid != NO_SUCH_CHILD_PID) { - /* - * This request seems to have hung - * - kill it - */ -#ifdef HAVE_PTHREAD_H - radlog(L_ERR, "Killing unresponsive thread for request %d", - request->number); - pthread_cancel(child_pid); -#endif - } /* else no proxy reply, quietly fail */ - - /* - * Maybe we haven't killed it. In that - * case, print a warning. - */ - } else -#endif - if ((child_pid != NO_SUCH_CHILD_PID) && - ((request->options & RAD_REQUEST_OPTION_LOGGED_CHILD) == 0)) { - radlog(L_ERR, "WARNING: Unresponsive child (id %lu) for request %d", - (unsigned long)child_pid, number); - - /* - * Set the option that we've sent a log message, - * so that we don't send more than one message - * per request. - */ - request->options |= RAD_REQUEST_OPTION_LOGGED_CHILD; - } - - /* - * Send a reject message for the request, mark it - * finished, and forget about the child. - */ - request_reject(request, REQUEST_FAIL_SERVER_TIMEOUT); - - request->child_pid = NO_SUCH_CHILD_PID; - -#ifdef DELETE_BLOCKED_REQUESTS - if (mainconfig.kill_unresponsive_children) - request->finished = TRUE; -#endif - return 0; - } /* else the request is still allowed to be in the queue */ - - /* - * If the request is finished, set the cleanup delay. - */ - if (request->finished) { - time_passed = mainconfig.cleanup_delay - time_passed; - goto setup_timeout; - } - - /* - * Accounting request. Don't re-send them, since the NAS - * will take care of doing that, and we're not a NAS. - * Instead, simply clean them up once we're pretty sure - * that the home server won't be responding. - */ - if ((request->packet->code == PW_ACCOUNTING_REQUEST) && - request->proxy && !request->proxy_reply && - (request->child_pid == NO_SUCH_CHILD_PID) && - ((info->now - request->proxy_start_time) > (mainconfig.proxy_retry_delay * 2))) { - goto cleanup; - } - - - /* - * Set reject delay, if appropriate. - */ - if ((request->packet->code == PW_AUTHENTICATION_REQUEST) && - (mainconfig.reject_delay > 0)) { - reject_delay: - time_passed = mainconfig.reject_delay - time_passed; - - /* - * This catches a corner case, apparently. - */ - if ((request->reply->code == PW_AUTHENTICATION_REJECT) && - (time_passed == 0)) goto reject_packet; - if (time_passed <= 0) time_passed = 1; - goto setup_timeout; - } - - /* - * The request is still alive, wake up when it's - * taken too long. - */ - time_passed = mainconfig.max_request_time - time_passed; - -setup_timeout: - if (time_passed < 0) time_passed = 1; - - if (time_passed < info->sleep_time) { - info->sleep_time = time_passed; - } - - return 0; -} - - -/* - * Clean up the request list, every so often. - * - * This is done by walking through ALL of the list, and - * - marking any requests which are finished, and expired - * - killing any processes which are NOT finished after a delay - * - deleting any marked requests. - * - * Returns the number of millisends to sleep, before processing - * something. - */ -int rl_clean_list(request_list_t *rl, time_t now) -{ - rl_walk_t info; - - info.now = now; - info.sleep_time = SLEEP_FOREVER; - info.rl = rl; - - lrad_packet_list_walk(rl->pl, &info, refresh_request); - - if (info.sleep_time < 0) info.sleep_time = 0; - - return info.sleep_time; -} diff --git a/src/main/request_process.c b/src/main/request_process.c deleted file mode 100755 index a9cdf35..0000000 --- a/src/main/request_process.c +++ /dev/null @@ -1,571 +0,0 @@ -/* - * proxy.c Proxy stuff. - * - * Version: $Id$ - * - * 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 - * - * Copyright 2000,2006 The FreeRADIUS server project - * Copyright 2000 Miquel van Smoorenburg - * Copyright 2000 Chris Parker - */ - -#include -RCSID("$Id$") - -#include - -#include - -#ifdef HAVE_NETINET_IN_H -# include -#endif - -#include -#include -#include -#include - -#include -#include -#include - - -/* - * Reprocess the request in possibly a child thread, only through - * a subsection of the post-proxy section of radiusd.conf. - */ -static int process_post_proxy_fail(REQUEST *request) -{ - VALUE_PAIR *vps; - - /* - * - */ - - - /* - * Hmm... this code is copied from below, which isn't good, - * and is similar to the code in rad_respond. - */ - switch (request->packet->code) { - /* - * Accounting requests, etc. get dropped on the floor. - */ - default: - case PW_ACCOUNTING_REQUEST: - case PW_STATUS_SERVER: - break; - - /* - * Authentication requests get their Proxy-State - * attributes copied over, and an otherwise blank - * reject message sent. - */ - case PW_AUTHENTICATION_REQUEST: - request->reply->code = PW_AUTHENTICATION_REJECT; - - /* - * Need to copy Proxy-State from request->packet->vps - */ - vps = paircopy2(request->packet->vps, PW_PROXY_STATE); - if (vps != NULL) - pairadd(&(request->reply->vps), vps); - break; - } - - /* - * Send the reply. The sender takes care of quenching - * packets. - */ - request->listener->send(request->listener, request); - - return 0; /* ignored for now */ -} - -/* - * For debugging - */ -static const LRAD_NAME_NUMBER request_fail_reason[] = { - { "no threads available to handle the request", - REQUEST_FAIL_NO_THREADS }, - - { "malformed RADIUS packet", - REQUEST_FAIL_DECODE}, - - { "pre-proxying failed", - REQUEST_FAIL_PROXY}, - - { "sending of the proxy packet failed", - REQUEST_FAIL_PROXY_SEND}, - - { "failure to be told how to respond", - REQUEST_FAIL_NO_RESPONSE}, - - { "no response from the home server", - REQUEST_FAIL_HOME_SERVER}, - - { "no response from the home server after multiple tries", - REQUEST_FAIL_HOME_SERVER2}, - - { "no response from the home server for a long period of time", - REQUEST_FAIL_HOME_SERVER3}, - - { "we were told to reject the request", - REQUEST_FAIL_NORMAL_REJECT}, - - { NULL, REQUEST_FAIL_UNKNOWN } -}; - - -/* - * Reject a request, by sending a trivial reply packet. - */ - void request_reject(REQUEST *request, request_fail_t reason) -{ - VALUE_PAIR *vps; - - /* - * Already rejected. Don't do anything. - */ - if (request->options & RAD_REQUEST_OPTION_REJECTED) { - return; - } - - DEBUG2("Server rejecting request %d due to %s.", - request->number, lrad_int2str(request_fail_reason, - reason, "unknown")); - - /* - * Remember that it was rejected. - */ - request->options |= RAD_REQUEST_OPTION_REJECTED; - - switch (reason) { - case REQUEST_FAIL_NO_THREADS: - DEBUG("WARNING: We recommend that you fix any TIMEOUT errors, or increase the value for \"max_servers\"."); - break; - - case REQUEST_FAIL_DECODE: - DEBUG("WARNING: Someone may be attacking your RADIUS server."); - break; - - case REQUEST_FAIL_NO_RESPONSE: - DEBUG("WARNING: You did not configure the server to accept, or reject the user. Double-check Auth-Type."); - break; - - /* - * If the home server goes down for some reason, - * we want to be able to know when. We do this - * by calling a sub-section of the post_proxy section, - * and processing any modules we find there. - * - * Note that this subsection CAN edit the response - * to the NAS. - */ - case REQUEST_FAIL_HOME_SERVER: /* Hmm... we may want only one */ - case REQUEST_FAIL_HOME_SERVER2: - case REQUEST_FAIL_HOME_SERVER3: - /* - * Conditionally disable the home server we sent - * packets to. - */ - realm_disable(request); - - /* - * Not supposed to re-process it, - */ - if (mainconfig.proxy_fail_type) { - DICT_VALUE *val; - - val = dict_valbyname(PW_POST_PROXY_TYPE, mainconfig.proxy_fail_type); - if (!val) { - DEBUG("ERROR: No such post-proxy type of \"%s\", cancelling post-proxy-failure call.", mainconfig.proxy_fail_type); - return; - } - - request->options |= RAD_REQUEST_OPTION_REPROCESS; - - thread_pool_addrequest(request, process_post_proxy_fail); - return; - } - break; - - case REQUEST_FAIL_SERVER_TIMEOUT: - radlog(L_ERR, "TIMEOUT for request %d in module %s, component %s", - request->number, - request->module ? request->module : "", - request->component ? request->component : ""); - request->options |= RAD_REQUEST_OPTION_STOP_NOW; - break; - - default: /* no additional messages, or things to do */ - break; - } - - switch (request->packet->code) { - /* - * Accounting requests, etc. get dropped on the floor. - */ - default: - case PW_ACCOUNTING_REQUEST: - case PW_STATUS_SERVER: - break; - - /* - * Authentication requests get their Proxy-State - * attributes copied over, and an otherwise blank - * reject message sent. - */ - case PW_AUTHENTICATION_REQUEST: - request->reply->code = PW_AUTHENTICATION_REJECT; - - /* - * Need to copy Proxy-State from request->packet->vps - */ - vps = paircopy2(request->packet->vps, PW_PROXY_STATE); - if (vps != NULL) - pairadd(&(request->reply->vps), vps); - break; - } - - /* - * Reject the request. The sender will take care of delaying - * or quenching rejects. - */ - request->listener->send(request->listener, request); -} - - -/* - * Respond to a request packet. - * - * Maybe we reply, maybe we don't. - * Maybe we proxy the request to another server, or else maybe - * we replicate it to another server. - */ -int rad_respond(REQUEST *request, RAD_REQUEST_FUNP fun) -{ - RADIUS_PACKET *packet, *original; - const char *secret; - int finished = FALSE; - - rad_assert(request->magic == REQUEST_MAGIC); - - /* - * Don't decode the packet if it's an internal "fake" - * request. Instead, just skip ahead to processing it. - */ - if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) != 0) { - goto skip_decode; - } - - /* - * Re-process the request. - */ - if ((request->options & RAD_REQUEST_OPTION_REPROCESS) != 0) { - goto skip_decode; - } - - /* - * Put the decoded packet into it's proper place. - */ - if (request->proxy_reply != NULL) { - packet = request->proxy_reply; - secret = request->proxysecret; - original = request->proxy; - } else { - packet = request->packet; - secret = request->secret; - original = NULL; - } - - /* - * Decode the packet, verifying it's signature, - * and parsing the attributes into structures. - * - * Note that we do this CPU-intensive work in - * a child thread, not the master. This helps to - * spread the load a little bit. - * - * Internal requests (ones that never go on the - * wire) have ->data==NULL (data is the wire - * format) and don't need to be "decoded" - */ - if (packet->data) { - int decoderesult; - - /* - * Fails verification: silently discard it. - */ - decoderesult = rad_verify(packet, original, secret); - if (decoderesult < 0) { - radlog(L_ERR, "%s Dropping packet without response.", librad_errstr); - /* Since accounting packets get this set in - * request_reject but no response is sent... - */ - request->options |= RAD_REQUEST_OPTION_REJECTED; - goto finished_request; - } - - /* - * Can't decode it. This usually means we're out - * of memory. - */ - decoderesult = rad_decode(packet, original, secret); - if (decoderesult < 0) { - radlog(L_ERR, "%s", librad_errstr); - request_reject(request, REQUEST_FAIL_DECODE); - goto finished_request; - } - } - - /* - * For proxy replies, remove non-allowed - * attributes from the list of VP's. - */ - if (request->proxy) { - int rcode; - rcode = proxy_receive(request); - switch (rcode) { - default: /* Don't Do Anything */ - break; - case RLM_MODULE_FAIL: - /* on error just continue with next request */ - goto next_request; - case RLM_MODULE_HANDLED: - /* if this was a replicated request, mark it as - * finished first, because it was postponed - */ - goto finished_request; - } - - } else { - /* - * This is the initial incoming request which - * we're processing. - * - * Some requests do NOT get cached, as they - * CANNOT possibly have duplicates. Set the - * magic option here. - * - * Status-Server messages are easy to generate, - * so we toss them as soon as we see a reply. - * - * Accounting-Request packets WITHOUT an - * Acct-Delay-Time attribute are NEVER - * duplicated, as RFC 2866 Section 4.1 says that - * the Acct-Delay-Time MUST be updated when the - * packet is re-sent, which means the packet - * changes, so it MUST have a new identifier and - * Request Authenticator. */ - if ((request->packet->code == PW_STATUS_SERVER) || - ((request->packet->code == PW_ACCOUNTING_REQUEST) && - (pairfind(request->packet->vps, PW_ACCT_DELAY_TIME) == NULL))) { - request->options |= RAD_REQUEST_OPTION_DONT_CACHE; - } - } - - skip_decode: - /* - * We should have a User-Name attribute now. - */ - if (request->username == NULL) { - request->username = pairfind(request->packet->vps, - PW_USER_NAME); - } - - (*fun)(request); - - /* - * If the request took too long to process, don't do - * anything else. - */ - if (request->options & RAD_REQUEST_OPTION_STOP_NOW) { - finished = TRUE; - goto postpone_request; - } - - /* - * If the request took too long to process, don't do - * anything else. - */ - if (request->options & RAD_REQUEST_OPTION_REJECTED) { - finished = TRUE; - goto postpone_request; - } - - /* - * Status-Server requests NEVER get proxied. - */ - if (mainconfig.proxy_requests) { - if ((request->packet->code != PW_STATUS_SERVER) && - ((request->options & RAD_REQUEST_OPTION_PROXIED) == 0)) { - int rcode; - - /* - * Try to proxy this request. - */ - rcode = proxy_send(request); - - switch (rcode) { - default: - break; - - /* - * There was an error trying to proxy the request. - * Drop it on the floor. - */ - case RLM_MODULE_FAIL: - DEBUG2("Error trying to proxy request %d: Rejecting it", request->number); - request_reject(request, REQUEST_FAIL_PROXY); - goto finished_request; - break; - - /* - * The pre-proxy module has decided to reject - * the request. Do so. - */ - case RLM_MODULE_REJECT: - DEBUG2("Request %d rejected in proxy_send.", request->number); - request_reject(request, REQUEST_FAIL_PROXY_SEND); - goto finished_request; - break; - - /* - * If the proxy code has handled the request, - * then postpone more processing, until we get - * the reply packet from the home server. - */ - case RLM_MODULE_HANDLED: - goto postpone_request; - break; - } - - /* - * Else rcode==RLM_MODULE_NOOP - * and the proxy code didn't do anything, so - * we continue handling the request here. - */ - } - } else if ((request->packet->code == PW_AUTHENTICATION_REQUEST) && - (request->reply->code == 0)) { - /* - * We're not configured to reply to the packet, - * and we're not proxying, so the DEFAULT behaviour - * is to REJECT the user. - */ - request_reject(request, REQUEST_FAIL_NO_RESPONSE); - goto finished_request; - } - - /* - * If we have a reply to send, copy the Proxy-State - * attributes from the request to the tail of the reply, - * and send the packet. - */ - rad_assert(request->magic == REQUEST_MAGIC); - if (request->reply->code != 0) { - VALUE_PAIR *vp = NULL; - - /* - * Need to copy Proxy-State from request->packet->vps - */ - vp = paircopy2(request->packet->vps, PW_PROXY_STATE); - if (vp) pairadd(&(request->reply->vps), vp); - } - - /* - * ALWAYS call the sender to send the reply. The sender - * will take care of doing the appropriate work to - * suppress packets which aren't supposed to be sent over - * the wire, or to be delayed. - */ - request->listener->send(request->listener, request); - - /* - * We're done processing the request, set the - * request to be finished, clean up as necessary, - * and forget about the request. - */ - -finished_request: - - /* - * Don't decode the packet if it's an internal "fake" - * request. Instead, just skip ahead to processing it. - */ - if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) != 0) { - goto skip_free; - } - - /* - * We're done handling the request. Free up the linked - * lists of value pairs. This might take a long time, - * so it's more efficient to do it in a child thread, - * instead of in the main handler when it eventually - * gets around to deleting the request. - * - * Also, no one should be using these items after the - * request is finished, and the reply is sent. Cleaning - * them up here ensures that they're not being used again. - * - * Hmm... cleaning them up in the child thread also seems - * to make the server run more efficiently! - * - * If we've delayed the REJECT, then do NOT clean up the request, - * as we haven't created the REJECT message yet. - */ - if ((request->options & RAD_REQUEST_OPTION_DELAYED_REJECT) == 0) { - if (request->packet) { - pairfree(&request->packet->vps); - request->username = NULL; - request->password = NULL; - } - - /* - * If we've sent a reply to the NAS, then this request is - * pretty much finished, and we have no more need for any - * of the value-pair's in it, including the proxy stuff. - */ - if (request->reply->code != 0) { - pairfree(&request->reply->vps); - } - } - - pairfree(&request->config_items); - if (request->proxy) { - pairfree(&request->proxy->vps); - } - if (request->proxy_reply) { - pairfree(&request->proxy_reply->vps); - } - - skip_free: - DEBUG2("Finished request %d", request->number); - finished = TRUE; - - /* - * Go to the next request, without marking - * the current one as finished. - * - * Hmm... this may not be the brightest thing to do. - */ -next_request: - DEBUG2("Going to the next request"); - -postpone_request: - return finished; -} diff --git a/src/main/threads.c b/src/main/threads.c index fe686a9..c29fa46 100644 --- a/src/main/threads.c +++ b/src/main/threads.c @@ -301,7 +301,7 @@ static int request_enqueue(REQUEST *request, RAD_REQUEST_FUNP fun) * Mark the request as done. */ radlog(L_ERR|L_CONS, "!!! ERROR !!! The server is blocked: discarding new request %d", request->number); - request->finished = TRUE; + request->child_state = REQUEST_DONE; return 0; } @@ -327,7 +327,7 @@ static int request_enqueue(REQUEST *request, RAD_REQUEST_FUNP fun) if (!lrad_fifo_push(thread_pool.fifo[fifo], entry)) { pthread_mutex_unlock(&thread_pool.queue_mutex); radlog(L_ERR, "!!! ERROR !!! Failed inserting request %d into the queue", request->number); - request->finished = TRUE; + request->child_state = REQUEST_DONE; return 0; } @@ -398,8 +398,8 @@ static int request_dequeue(REQUEST **request, RAD_REQUEST_FUNP *fun) * The main clean-up code won't delete the request from * the request list, until it's marked "finished" */ - if ((*request)->options & RAD_REQUEST_OPTION_STOP_NOW) { - (*request)->finished = 1; + if ((*request)->master_state == REQUEST_STOP_PROCESSING) { + (*request)->child_state = REQUEST_DONE; goto retry; } @@ -451,8 +451,6 @@ static void *request_handler_thread(void *arg) * Loop forever, until told to exit. */ do { - int finished; - /* * Wait to be signalled. */ @@ -491,37 +489,12 @@ static void *request_handler_thread(void *arg) self->thread_num, self->request->number, self->request_count); - /* - * Respond, and reset request->child_pid - */ - finished = rad_respond(self->request, fun); + radius_handle_request(self->request, fun); /* * Update the active threads. */ pthread_mutex_lock(&thread_pool.queue_mutex); - - /* - * We haven't replied to the client, but we HAVE - * sent a proxied packet, and we have NOT - * received a proxy response. In that case, send - * the proxied packet now. Doing this in the mutex - * avoids race conditions. - * - * FIXME: this work should really depend on a - * "state", and "next handler", rather than - * horrid hacks like thise. - */ - if (!self->request->reply->data && - self->request->proxy && self->request->proxy->data - && !self->request->proxy_reply) - self->request->proxy_listener->send(self->request->proxy_listener, - self->request); - - self->request->child_pid = NO_SUCH_CHILD_PID; - self->request->finished = finished; - self->request = NULL; - rad_assert(thread_pool.active_threads > 0); thread_pool.active_threads--; pthread_mutex_unlock(&thread_pool.queue_mutex); @@ -852,7 +825,7 @@ int thread_pool_addrequest(REQUEST *request, RAD_REQUEST_FUNP fun) * We've been told not to spawn threads, so don't. */ if (!thread_pool.spawn_flag) { - request->finished = rad_respond(request, fun); + radius_handle_request(request, fun); /* * Requests that care about child process exit diff --git a/src/main/util.c b/src/main/util.c index 8bc3362..960d470 100644 --- a/src/main/util.c +++ b/src/main/util.c @@ -245,7 +245,6 @@ void request_free(REQUEST **request_ptr) #ifndef NDEBUG request->magic = 0x01020304; /* set the request to be nonsense */ strcpy(request->secret, "REQUEST-DELETED"); - strcpy(request->proxysecret, "REQUEST-DELETED"); #endif free(request); @@ -369,7 +368,6 @@ REQUEST *request_alloc(void) request->password = NULL; request->timestamp = time(NULL); request->child_pid = NO_SUCH_CHILD_PID; - request->container = NULL; request->options = RAD_REQUEST_OPTION_NONE; return request; @@ -390,7 +388,6 @@ REQUEST *request_alloc_fake(REQUEST *oldreq) request->number = oldreq->number; request->child_pid = NO_SUCH_CHILD_PID; - request->options = RAD_REQUEST_OPTION_FAKE_REQUEST; request->packet = rad_alloc(0); rad_assert(request->packet != NULL); @@ -406,6 +403,11 @@ REQUEST *request_alloc_fake(REQUEST *oldreq) request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK); request->packet->dst_ipaddr = request->packet->src_ipaddr; request->packet->src_port = request->number >> 8; + + /* + * This field is used by the rest of the code to notice that the + * request is "internal", and not from a real client. + */ request->packet->dst_port = 0; /* diff --git a/src/modules/rlm_detail/rlm_detail.c b/src/modules/rlm_detail/rlm_detail.c index e7b4805..e023217 100644 --- a/src/modules/rlm_detail/rlm_detail.c +++ b/src/modules/rlm_detail/rlm_detail.c @@ -205,9 +205,7 @@ static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet, int locked; int lock_count; struct timeval tv; - REALM *proxy_realm; - char proxy_buffer[16]; - VALUE_PAIR *pair = packet->vps; + VALUE_PAIR *pair; struct detail_instance *inst = instance; @@ -428,7 +426,7 @@ static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet, } /* Write each attribute/value to the log file */ - for (; pair != NULL; pair = pair->next) { + for (pair = packet->vps; pair != NULL; pair = pair->next) { DICT_ATTR da; da.attr = pair->attribute; @@ -452,23 +450,18 @@ static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet, * Add non-protocol attibutes. */ if (compat) { - if ((pair = pairfind(request->config_items, - PW_PROXY_TO_REALM)) != NULL) { - proxy_realm = realm_find(pair->vp_strvalue, TRUE); - if (proxy_realm) { - memset((char *) proxy_buffer, 0, 16); - - rad_assert(proxy_realm->acct_ipaddr.af == AF_INET); - - inet_ntop(proxy_realm->acct_ipaddr.af, - &proxy_realm->acct_ipaddr.ipaddr, - proxy_buffer, sizeof(proxy_buffer)); - fprintf(outfp, "\tFreeradius-Proxied-To = %s\n", + if (request->proxy) { + char proxy_buffer[128]; + + inet_ntop(request->proxy->dst_ipaddr.af, + &request->proxy->dst_ipaddr.ipaddr, + proxy_buffer, sizeof(proxy_buffer)); + fprintf(outfp, "\tFreeradius-Proxied-To = %s\n", proxy_buffer); - DEBUG("rlm_detail: Freeradius-Proxied-To set to %s", + DEBUG("rlm_detail: Freeradius-Proxied-To = %s", proxy_buffer); - } } + fprintf(outfp, "\tTimestamp = %ld\n", (unsigned long) request->timestamp); diff --git a/src/modules/rlm_eap/eap.c b/src/modules/rlm_eap/eap.c index 0cdd32e..3486ad0 100644 --- a/src/modules/rlm_eap/eap.c +++ b/src/modules/rlm_eap/eap.c @@ -252,7 +252,7 @@ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) * We don't do TLS inside of TLS, as it's a bad * idea... */ - if (((handler->request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) != 0) && + if ((handler->request->packet->dst_port == 0) && (default_eap_type == PW_EAP_TLS)) { DEBUG2(" rlm_eap: Unable to tunnel TLS inside of TLS"); return EAP_INVALID; @@ -645,9 +645,8 @@ int eap_start(rlm_eap_t *inst, REQUEST *request) * If it's a LOCAL realm, then we're not proxying * to it. */ - realm = realm_find(proxy->vp_strvalue, 0); - rad_assert(realm->ipaddr.af == AF_INET); - if (realm && (realm->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE))) { + realm = realm_find(proxy->vp_strvalue); + if (realm && (realm->auth_pool == NULL)) { proxy = NULL; } } diff --git a/src/modules/rlm_eap/rlm_eap.c b/src/modules/rlm_eap/rlm_eap.c index 591361e..dcaf53c 100644 --- a/src/modules/rlm_eap/rlm_eap.c +++ b/src/modules/rlm_eap/rlm_eap.c @@ -261,7 +261,7 @@ static int eap_authenticate(void *instance, REQUEST *request) * If it's a recursive request, then disallow * TLS, TTLS, and PEAP, inside of the TLS tunnel. */ - if ((request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) != 0) { + if (request->packet->dst_port == 0) { switch(handler->eap_ds->response->type.type) { case PW_EAP_TLS: case PW_EAP_TTLS: @@ -625,7 +625,7 @@ static int eap_post_proxy(void *inst, REQUEST *request) */ i = 34; /* starts off with 34 octets */ len = rad_tunnel_pwdecode(vp->vp_strvalue + 17, &i, - request->proxysecret, + request->home_server->secret, request->proxy->vector); /* diff --git a/src/modules/rlm_pap/rlm_pap.c b/src/modules/rlm_pap/rlm_pap.c index 504df4d..ec0c8c4 100644 --- a/src/modules/rlm_pap/rlm_pap.c +++ b/src/modules/rlm_pap/rlm_pap.c @@ -376,10 +376,8 @@ static int pap_authorize(void *instance, REQUEST *request) */ case PW_PROXY_TO_REALM: { - REALM *realm = realm_find(vp->vp_strvalue, 0); - if (realm && - (realm->ipaddr.af == AF_INET) && - (realm->ipaddr.ipaddr.ip4addr.s_addr != htonl(INADDR_NONE))) { + REALM *realm = realm_find(vp->vp_strvalue); + if (realm && !realm->auth_pool) { return RLM_MODULE_NOOP; } break; @@ -408,6 +406,14 @@ static int pap_authorize(void *instance, REQUEST *request) * Print helpful warnings if there was no password. */ if (!found_pw) { + /* + * Likely going to be proxied. Avoid printing + * warning message. + */ + if (pairfind(request->config_items, PW_REALM) || + (pairfind(request->config_items, PW_PROXY_TO_REALM))) { + return RLM_MODULE_NOOP; + } DEBUG("rlm_pap: WARNING! No \"known good\" password found for the user. Authentication may fail because of this."); return RLM_MODULE_NOOP; } diff --git a/src/modules/rlm_realm/rlm_realm.c b/src/modules/rlm_realm/rlm_realm.c index baffe84..34598cd 100644 --- a/src/modules/rlm_realm/rlm_realm.c +++ b/src/modules/rlm_realm/rlm_realm.c @@ -174,20 +174,20 @@ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm /* * Allow DEFAULT realms unless told not to. */ - realm = realm_find(realmname, (request->packet->code == PW_ACCOUNTING_REQUEST)); + realm = realm_find(realmname); if (!realm) { DEBUG2(" rlm_realm: No such realm \"%s\"", (realmname == NULL) ? "NULL" : realmname); return 0; } if( inst->ignore_default && - (strcmp(realm->realm, "DEFAULT")) == 0) { + (strcmp(realm->name, "DEFAULT")) == 0) { DEBUG2(" rlm_realm: Found DEFAULT, but skipping due to config."); return 0; } - DEBUG2(" rlm_realm: Found realm \"%s\"", realm->realm); + DEBUG2(" rlm_realm: Found realm \"%s\"", realm->name); /* * If we've been told to strip the realm off, then do so. @@ -217,14 +217,14 @@ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm } DEBUG2(" rlm_realm: Proxying request from user %s to realm %s", - username, realm->realm); + username, realm->name); /* * Add the realm name to the request. */ - pairadd(&request->packet->vps, pairmake("Realm", realm->realm, + pairadd(&request->packet->vps, pairmake("Realm", realm->name, T_OP_EQ)); - DEBUG2(" rlm_realm: Adding Realm = \"%s\"", realm->realm); + DEBUG2(" rlm_realm: Adding Realm = \"%s\"", realm->name); /* * Figure out what to do with the request. @@ -239,30 +239,20 @@ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm * Perhaps accounting proxying was turned off. */ case PW_ACCOUNTING_REQUEST: - if (realm->acct_ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)) { + if (!realm->acct_pool) { DEBUG2(" rlm_realm: Accounting realm is LOCAL."); return 0; } - - if (realm->acct_port == 0) { - DEBUG2(" rlm_realm: acct_port is not set. Proxy cancelled."); - return 0; - } break; /* * Perhaps authentication proxying was turned off. */ case PW_AUTHENTICATION_REQUEST: - if (realm->ipaddr.ipaddr.ip4addr.s_addr == htonl(INADDR_NONE)) { + if (!realm->auth_pool) { DEBUG2(" rlm_realm: Authentication realm is LOCAL."); return 0; } - - if (realm->auth_port == 0) { - DEBUG2(" rlm_realm: auth_port is not set. Proxy cancelled."); - return 0; - } break; } @@ -271,19 +261,26 @@ static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm * that has already proxied the request, we don't need to do * it again. */ - for (vp = request->packet->vps; vp; vp = vp->next) { - if (vp->attribute == PW_FREERADIUS_PROXIED_TO) { - if (request->packet->code == PW_AUTHENTICATION_REQUEST && - vp->lvalue == realm->ipaddr.ipaddr.ip4addr.s_addr) { - DEBUG2(" rlm_realm: Request not proxied due to Freeradius-Proxied-To"); - return 0; - } - if (request->packet->code == PW_ACCOUNTING_REQUEST && - vp->lvalue == realm->acct_ipaddr.ipaddr.ip4addr.s_addr) { - DEBUG2(" rlm_realm: Request not proxied due to Freeradius-Proxied-To"); - return 0; - } + vp = pairfind(request->packet->vps, PW_FREERADIUS_PROXIED_TO); + if (vp) { +#if 0 + /* + * FIXME: HOME SERVER + * + * What the heck is this code doing, and why? + */ + + if (request->packet->code == PW_AUTHENTICATION_REQUEST && + vp->lvalue == realm->home_auth->ipaddr.ipaddr.ip4addr.s_addr) { + DEBUG2(" rlm_realm: Request not proxied due to Freeradius-Proxied-To"); + return 0; + } + if (request->packet->code == PW_ACCOUNTING_REQUEST && + vp->lvalue == realm->home_acct->ipaddr.ipaddr.ip4addr.s_addr) { + DEBUG2(" rlm_realm: Request not proxied due to Freeradius-Proxied-To"); + return 0; } +#endif } /* @@ -304,7 +301,7 @@ static void add_proxy_to_realm(VALUE_PAIR **vps, REALM *realm) * Tell the server to proxy this request to another * realm. */ - vp = pairmake("Proxy-To-Realm", realm->realm, T_OP_EQ); + vp = pairmake("Proxy-To-Realm", realm->name, T_OP_EQ); if (!vp) { radlog(L_ERR|L_CONS, "no memory"); exit(1); @@ -388,7 +385,7 @@ static int realm_authorize(void *instance, REQUEST *request) * Maybe add a Proxy-To-Realm attribute to the request. */ DEBUG2(" rlm_realm: Preparing to proxy authentication request to realm \"%s\"\n", - realm->realm); + realm->name); add_proxy_to_realm(&request->config_items, realm); return RLM_MODULE_UPDATED; /* try the next module */ @@ -424,7 +421,7 @@ static int realm_preacct(void *instance, REQUEST *request) * Maybe add a Proxy-To-Realm attribute to the request. */ DEBUG2(" rlm_realm: Preparing to proxy accounting request to realm \"%s\"\n", - realm->realm); + realm->name); add_proxy_to_realm(&request->config_items, realm); return RLM_MODULE_UPDATED; /* try the next module */ -- 2.1.4