Handle empty lists better
[freeradius.git] / src / modules / rlm_replicate / rlm_replicate.c
1 /*
2  * rlm_replicate.c
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2011,2012  The FreeRADIUS server project
21  */
22
23 #include <freeradius-devel/ident.h>
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28
29 #ifdef WITH_PROXY
30 static void cleanup(RADIUS_PACKET *packet)
31 {
32         if (!packet) return;
33         if (packet->sockfd >= 0) close(packet->sockfd);
34         rad_free(&packet);
35 }
36
37 /** Copy packet to multiple servers
38  *
39  * Create a duplicate of the packet and send it to a list of realms
40  * defined by the presence of the Replicate-To-Realm VP in the control
41  * list of the current request.
42  * 
43  * This is pretty hacky and is 100% fire and forget. If you're looking
44  * to forward authentication requests to multiple realms and process
45  * the responses, this function will not allow you to do that.
46  *
47  * @param[in] instance  of this module.
48  * @param[in] request   The current request.
49  * @param[in] list      of attributes to copy to the duplicate packet.
50  * @param[in] code      to write into the code field of the duplicate packet.
51  * @return RCODE fail on error, invalid if list does not exist, noop if no
52  *         replications succeeded, else ok.
53  */
54 static int replicate_packet(void *instance, REQUEST *request,
55                             pair_lists_t list, unsigned int code)
56 {
57         int rcode = RLM_MODULE_NOOP;
58         VALUE_PAIR *vp, **vps, *last;
59         home_server *home;
60         REALM *realm;
61         home_pool_t *pool;
62         RADIUS_PACKET *packet = NULL;
63
64         instance = instance;    /* -Wunused */
65         last = request->config_items;
66
67         /*
68          *      Send as many packets as necessary to different
69          *      destinations.
70          */
71         while (1) {
72                 vp = pairfind(last, PW_REPLICATE_TO_REALM, 0);
73                 if (!vp) break;
74
75                 last = vp->next;
76
77                 realm = realm_find2(vp->vp_strvalue);
78                 if (!realm) {
79                         RDEBUG2("ERROR: Cannot Replicate to unknown realm %s", realm);
80                         continue;
81                 }
82                 
83                 /*
84                  *      We shouldn't really do this on every loop.
85                  */
86                 switch (request->packet->code) {
87                 default:
88                         RDEBUG2("ERROR: Cannot replicate unknown packet code %d",
89                                 request->packet->code);
90                         cleanup(packet);
91                         return RLM_MODULE_FAIL;
92                 
93                 case PW_AUTHENTICATION_REQUEST:
94                         pool = realm->auth_pool;
95                         break;
96                         
97 #ifdef WITH_ACCOUNTING
98                         
99                 case PW_ACCOUNTING_REQUEST:
100                         pool = realm->acct_pool;
101                         break;
102 #endif
103                         
104 #ifdef WITH_COA
105                 case PW_COA_REQUEST:
106                 case PW_DISCONNECT_REQUEST:
107                         pool = realm->acct_pool;
108                         break;
109 #endif
110                 }
111                 
112                 if (!pool) {
113                         RDEBUG2(" WARNING: Cancelling replication to Realm %s, as the realm is local.", realm->name);
114                         continue;
115                 }
116                 
117                 home = home_server_ldb(realm->name, pool, request);
118                 if (!home) {
119                         RDEBUG2("ERROR: Failed to find live home server for realm %s",
120                                 realm->name);
121                         continue;
122                 }
123                 
124                 /*
125                  *      For replication to multiple servers we re-use the packet
126                  *      we built here.
127                  */
128                 if (!packet) {
129                         packet = rad_alloc(1);
130                         if (!packet) return RLM_MODULE_FAIL;
131                         packet->sockfd = -1;
132                         packet->code = code;
133                         packet->id = fr_rand() & 0xff;
134
135                         packet->sockfd = fr_socket(&home->src_ipaddr, 0);
136                         if (packet->sockfd < 0) {
137                                 RDEBUG("ERROR: Failed opening socket: %s", fr_strerror());
138                                 rcode = RLM_MODULE_FAIL;
139                                 goto done;
140                         }
141                         
142                         vps = radius_list(request, list);
143                         if (!vps) {
144                                 RDEBUG("WARNING: List '%s' doesn't exist for "
145                                        "this packet", fr_int2str(pair_lists,
146                                        list, "¿unknown?"));
147                                 rcode = RLM_MODULE_INVALID;
148                                 goto done;
149                         }
150                         
151                         /*
152                          *      Don't assume the list actually contains any
153                          *      attributes.
154                          */
155                         if (*vps) {
156                                 packet->vps = paircopy(*vps);
157                                 if (!packet->vps) {
158                                         RDEBUG("ERROR: Out of memory!");
159                                         rcode = RLM_MODULE_FAIL;
160                                         goto done;
161                                 }
162                         }
163                         
164
165
166                         /*
167                          *      For CHAP, create the CHAP-Challenge if
168                          *      it doesn't exist.
169                          */
170                         if ((code == PW_AUTHENTICATION_REQUEST) &&
171                             (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0) != NULL) &&
172                             (pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0) == NULL)) {
173                                 vp = radius_paircreate(request, &packet->vps,
174                                                        PW_CHAP_CHALLENGE, 0,
175                                                        PW_TYPE_OCTETS);
176                                 vp->length = AUTH_VECTOR_LEN;
177                                 memcpy(vp->vp_strvalue, request->packet->vector,
178                                        AUTH_VECTOR_LEN);
179                         }
180                 } else {
181                         size_t i;
182
183                         for (i = 0; i < sizeof(packet->vector); i++) {
184                                 packet->vector[i] = fr_rand() & 0xff;
185                         }
186
187                         packet->id++;
188                         free(packet->data);
189                         packet->data = NULL;
190                         packet->data_len = 0;
191                 }
192
193                 /*
194                  *      (Re)-Write these.
195                  */
196                 packet->dst_ipaddr = home->ipaddr;
197                 packet->dst_port = home->port;
198                 memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr));
199                 packet->src_port = 0;
200                 
201                 /*
202                  *      Encode, sign and then send the packet.
203                  */
204                 RDEBUG("Replicating list '%s' to Realm '%s'",
205                        fr_int2str(pair_lists, list, "¿unknown?"),realm->name);
206                 if (rad_send(packet, NULL, home->secret) < 0) {
207                         RDEBUG("ERROR: Failed replicating packet: %s",
208                                fr_strerror());
209                         rcode = RLM_MODULE_FAIL;
210                         goto done;
211                 }
212
213                 /*
214                  *      We've sent it to at least one destination.
215                  */
216                 rcode = RLM_MODULE_OK;
217         }
218         
219         done:
220         
221         cleanup(packet);
222         return rcode;
223 }
224 #else
225 static int replicate_packet(void *instance, REQUEST *request,
226                             pair_lists_t list, unsigned int code)
227 {
228         RDEBUG("Replication is unsupported in this build.");
229         return RLM_MODULE_FAIL;
230 }
231 #endif
232
233 static int replicate_authorize(void *instance, REQUEST *request)
234 {
235         return replicate_packet(instance, request, PAIR_LIST_REQUEST,
236                                 request->packet->code);
237 }
238
239 static int replicate_preaccounting(void *instance, REQUEST *request)
240 {
241         return replicate_packet(instance, request, PAIR_LIST_REQUEST,
242                                 request->packet->code);
243 }
244
245 static int replicate_accounting(void *instance, REQUEST *request)
246 {
247         return replicate_packet(instance, request, PAIR_LIST_REPLY,
248                                 request->reply->code);
249 }
250
251 static int replicate_preproxy(void *instance, REQUEST *request)
252 {
253         return replicate_packet(instance, request, PAIR_LIST_PROXY_REQUEST,
254                                 request->proxy->code);
255 }
256
257 static int replicate_postproxy(void *instance, REQUEST *request)
258 {
259         return replicate_packet(instance, request, PAIR_LIST_PROXY_REPLY,
260                                 request->proxy_reply->code);
261 }
262
263 static int replicate_postauth(void *instance, REQUEST *request)
264 {
265         return replicate_packet(instance, request, PAIR_LIST_REPLY,
266                                 request->reply->code);
267 }
268
269 static int replicate_coarequest(void *instance, REQUEST *request)
270 {
271         return replicate_packet(instance, request, PAIR_LIST_REQUEST,
272                                 request->packet->code);
273 }
274
275 /*
276  *      The module name should be the only globally exported symbol.
277  *      That is, everything else should be 'static'.
278  *
279  *      If the module needs to temporarily modify it's instantiation
280  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
281  *      The server will then take care of ensuring that the module
282  *      is single-threaded.
283  */
284 module_t rlm_replicate = {
285         RLM_MODULE_INIT,
286         "replicate",
287         RLM_TYPE_THREAD_SAFE,           /* type */
288         NULL,                           /* instantiation */
289         NULL,                           /* detach */
290         {
291                 NULL,                   /* authentication */
292                 replicate_authorize,    /* authorization */
293                 replicate_preaccounting,/* preaccounting */
294                 replicate_accounting,   /* accounting */
295                 NULL,                   /* checksimul */
296                 replicate_preproxy,     /* pre-proxy */
297                 replicate_postproxy,    /* post-proxy */
298                 replicate_postauth      /* post-auth */
299 #ifdef WITH_COA
300                 , replicate_coarequest, /* coa-request */
301                 NULL
302 #endif
303         },
304 };