"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 /* ====================================================================
2 * Copyright (c) 1997-2002 The Apache Group. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. All advertising materials mentioning features or use of this
17 * software must display the following acknowledgment:
18 * "This product includes software developed by the Apache Group
19 * for use in the Apache HTTP server project (http://www.apache.org/)."
20 *
21 * 4. The names "Apache Server" and "Apache Group" must not be used to
22 * endorse or promote products derived from this software without
23 * prior written permission.
24 *
25 * 5. Redistributions of any form whatsoever must retain the following
26 * acknowledgment:
27 * "This product includes software developed by the Apache Group
28 * for use in the Apache HTTP server project (http://www.apache.org/)."
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
31 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
34 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
38 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
41 * OF THE POSSIBILITY OF SUCH DAMAGE.
42 * ====================================================================
43 *
44 * This software consists of voluntary contributions made by many
45 * individuals on behalf of the Apache Group and was originally based
46 * on public domain software written at the National Center for
47 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
48 * For more information on the Apache Group and the Apache HTTP server
49 * project, please see <http://www.apache.org/>.
50 *
51 *
52 * CVS $Id: mod_auth_radius.c,v 1.15 2003/03/24 19:16:15 aland Exp $
53 */
54
55 /*
56 Everyone wants strong authentication over the web. For us, this means
57 RADIUS.
58
59 Using static passwords & RADIUS authentication over the web is a BAD IDEA.
60 Everyone can sniff the passwords, as they're sent over the net in the clear.
61 RADIUS web authentication is a REALLY BAD IDEA if you use the same RADIUS
62 server for web and NAS (dial-up) or firewall users. Then ANYONE can
63 pretend to be you, and break through your firewall with minimal effort.
64
65 PLEASE use a different RADIUS server for web authentication and dial-up
66 or firewall users! If you must use the same server, go for one-time
67 passwords. They're ever so much more secure.
68
69 Also, do NOT have your RADIUS server visible to the external world.
70 Doing so makes all kinds of attacks possible.
71
72 **************************************************
73
74 Add to Configuration file BEFORE mod_auth.o:
75 Module radius_auth_module mod_auth_radius.o
76
77 Add to server configuration file
78 AddRadiusAuth <server>[:port] <secret> [<seconds>[:<retries>]]
79 AddRadiusCookieValid <minutes>
80 AddModule modules/extra/mod_auth_radius.o (for 1.3.x)
81
82 Add to directory configuration
83 AddRadiusAuth <server>[:port] <secret> [<seconds>]
84 AuthRadiusBindAddress <local address/interface>
85 AuthRadiusAuthoritative on
86 AuthRadiusActive on
87 AuthRadiusCookieValid <minutes>
88
89 **************************************************
90
91 Adding mod_auth_radius to the Configuration file before mod_auth
92 allows you to have mod_auth_radius authoritative by default, but NOT
93 have it interfere with the rest of your configuration. The authentication
94 methods are tried from the bottom of the list, on up.
95
96 You must have at least one authentication method as authoritative. If
97 they all return "DECLINED", you get "server configuration error" message.
98
99 AddRadiusAuth configures the RADIUS server name (and optional port).
100 You must also specify the shared secret, and tell the RADIUS server that
101 the web host machine is a valid RADIUS client. The optional <seconds> field
102 specifies how long Apache waits before giving up, and deciding that the
103 RADIUS server is down. It then returns a "DENIED" error.
104
105 If you want, you can specify how long the returned cookies are valid.
106 The time is in minutes, with the magic value of '0' meaning forever.
107
108
109 The per-dir configuration Cookie Valid time does NOT over-ride the server
110 configuration. mod_auth_radius choose the most restrictive of the two to
111 use. This way, a site administrator can say all cookies are valid forever,
112 and then make some directories a bit more secure, by forcing
113 re-authentication every hour.
114
115 If you want logging, use the standard Apache access log. A log message
116 is generated ONLY when a user has authenticated, and their name & file
117 accessed is put in the log file.
118
119 How it works
120 ============
121
122 The browser requests a page: http://www.example.com/index.html
123
124 Apache notes that the directory is access controlled, and sends a
125 "Authorization Required".
126
127 The browser asks for a username & password, which it then sends to Apache,
128 along with a request for the page again.
129
130 Apache calls mod_auth_radius, which notes that there is no RADIUS cookie
131 in the request.
132
133 mod_auth_radius packages up the username/password into a RADIUS request,
134 and sends it to the RADIUS server.
135
136 The RADIUS server does its magic, and decides yes/no for authentication.
137
138 If no, mod_auth_radius returns DENIED.
139
140 If yes, mod_auth_radius returns a cookie containing MD5'd public+private
141 information.
142
143 The web browser uses this cookie on all subsequent requests, and
144 mod_auth_radius verifies the cookie is valid, and doesn't contact the
145 RADIUS server again.
146
147 Some caveats
148 ============
149
150 This works fine for static passwords (i.e. "user", "password"), but needs
151 a bit more attention for one-time passwords. All of the browsers I've
152 tested don't use the cookie immediately if you're accessing a directory
153 as:
154
155 http://www.example.com/
156
157 What's hidden here is that the following files are checked for:
158
159 http://www.example.com/
160 http://www.example.com/home.html
161 http://www.example.com/home.cgi
162 http://www.example.com/index.cgi
163 http://www.example.com/index.html
164
165 etc., all in sequence. This module does a 'stat', and returns "NOT FOUND"
166 when anyone tries to access a file which doesn't exist. However,
167 it WILL authenticate for a file which does exists, but the browser may
168 not use the returned cookie when accessing a different page.
169
170 The way to fix this is to point the browser at a specific page. i.e.
171
172 http://www.example.com/
173 says "connect to our _secure_ site", where _secure_ is a link to
174
175 http://www.example.com/secure/index.html
176
177
178 People using static passwords don't need to do this, but if they don't,
179 they'll notice that their RADIUS server is getting 1-4 hits for every web
180 authentication request.
181
182
183 Some browsers (I.E.) have a problem with sending cookies on initial
184 requests. If you have a file index.html which includes img/foo.gif
185 in the same directory. The user authenticates, reads index.html
186 (with the cookie in the request header), BUT on reading the gifs,
187 the cookie is NOT included.
188
189 This problem can be avoided by EITHER putting the gifs in the same
190 directory as the index.html file, or putting moving the entire tree
191 down a node, and having a NEW index.html which points to ./moved/index.html
192 This is ridiculously ugly, but it seems to work.
193
194
195 About the cookies
196 =================
197
198 The cookies are valid for a specified time, or until the browser dies.
199 mod_auth_radius will forcibly try to expire cookies that it thinks are
200 too old. If your browser doesn't expire the cookie, you'll see an
201 authorization required message over and over. You must then exit the
202 browser, and re-load the web page.
203
204 Any questions or comments can be sent to me at: aland@freeradius.org
205
206
207 Challenge-Response support
208 ==========================
209
210 As of 1.2.1, this module supports the full RADIUS challenge-response
211 mechanism. From the user's perspective, on authenticatation, type
212 in username & garbage (or NUL) password. Click <OK>, and you'll get
213 an authentication failure. This is fine, as mod_auth_radius has secretly
214 set a cookie, and modified the Basic-Authentication-Realm.
215
216 When the authentication fails, click <OK> to continue, and you'll get
217 another username/password authentication window. This time, however,
218 you'll see your username displayed, along with the RADIUS Reply-Message
219 at the top of the authentication window. This message usually includes
220 a challenge.
221
222 Type in your username, and put the response to the challenge in the password
223 field. Click <OK> again, and you should be authenticated.
224
225 The secret is that cookies are being magically set back and forth, and these
226 cookies include the RADIUS state variable.
227
228 The challenge-response works on Netscape 3.x and 4.x, HotJava, but NOT
229 on Internet Explorer. I.E. does not appear to follow the relevant RFCs
230 properly.
231
232
233 Version History
234 ===============
235
236 1.5.4 Support for retries from John Lines <john.lines@integris.co.uk>
237
238 1.5.3 Bug fix from Bryan Stansell <bryan@stansell.org>, to set
239 the right data element for the AddRadiusCookieValid configuration
240 item.
241
242 1.5.2 Updates for NAS-Identifier and NAS-IP-Address, based on ideas
243 from Adrian Hosey <ahosey@systhug.com>. The NAS-Identifier is
244 the virtual server host name, and the NAS-IP-Address is the
245 IP address of the base server.
246
247 Also integrated code from http://www.wede.de/sw/mod_auth_radius/
248 which had forked form this one after v1.3.3.
249
250 1.5.1 Quick release, for bug found by f.garosi@usl7.toscana.it.
251
252 1.5.0 Don't stat() proxy requests.
253
254 1.3.3 Another minor bug fix and configuration hints for Apache 1.3.x
255 Thanks to Hiroshi MIZOGUCHI <mizoguti@screen.co.jp>.
256
257 1.3.2 Fixed a bug which sometimes caused a SEGV in debugging mode.
258 Thanks to Tomi Leppikangas <tomilepp@ousrvr2.oulu.fi> for
259 pointing it out.
260
261 1.3.1 (minor) Added more error output on failed response
262
263 1.3.0 Fixed for Apache 1.3.0
264
265 1.2.5 Corrected typo in sscanf
266
267 1.2.4 Added support for debugging, so people can see what's going
268 on during the authentication process. Define DEBUG_RADIUS
269 in the code below to enable debugging.
270
271 1.2.3 Corrected some problems with normal username/password
272 authentication and re-loads.
273
274 1.2.2: Cleaned up usage of IP addresses
275 return failure on unknown RADIUS response code.
276
277 1.2.1: Finalized challenge/response & tested it
278
279 1.2 : Cookies are expired on authentication failure.
280 Add to r->err_headers_out, NOT r->headers_out.
281
282 1.1 : Bug fixes ("forever" is one month, not 12 minutes)
283 Added proper error outputs
284
285 1.0 : Initial version.
286
287 */
288
289 #include "httpd.h"
290 #include "http_config.h"
291 #include "http_core.h"
292 #include "http_log.h"
293 #include "http_protocol.h"
294 #include "util_md5.h"
295 #include "ap_compat.h"
296
297 module radius_auth_module;
298
299 /* define DEBUG_RADIUS for lots of status messages */
300 #define DEBUG_RADIUS
301 #ifdef DEBUG_RADIUS
302 #define DPRINTF printf
303 #else
304 #define DPRINTF
305 #endif DEBUG_RADIUS
306
307 /*
308 RFC 2138 says that this port number is wrong, but everyone's using it.
309 Use " AddRadiusAuth server:port secret " to change the port manually.
310 */
311 #define RADIUS_AUTH_UDP_PORT 1645
312
313 #define RADIUS_PASSWORD_LEN 16
314 #define RADIUS_RANDOM_VECTOR_LEN 16
315
316 /* Per-attribute structure */
317 typedef struct attribute_t {
318 unsigned char attribute;
319 unsigned char length;
320 unsigned char data[1];
321 } attribute_t;
322
323 /* Packet header structure */
324 typedef struct radius_packet_t {
325 unsigned char code;
326 unsigned char id;
327 unsigned short length;
328 unsigned char vector[RADIUS_RANDOM_VECTOR_LEN];
329 attribute_t first;
330 } radius_packet_t;
331
332 #define RADIUS_HEADER_LEN 20
333
334 /* RADIUS ID definitions. See RFC 2138 */
335 #define RADIUS_ACCESS_REQUEST 1
336 #define RADIUS_ACCESS_ACCEPT 2
337 #define RADIUS_ACCESS_REJECT 3
338 #define RADIUS_ACCESS_CHALLENGE 11
339
340 /* RADIUS attribute definitions. Also from RFC 2138 */
341 #define RADIUS_USER_NAME 1
342 #define RADIUS_PASSWORD 2
343 #define RADIUS_NAS_IP_ADDRESS 4
344 #define RADIUS_SERVICE_TYPE 6
345 #define RADIUS_REPLY_MESSAGE 18
346 #define RADIUS_STATE 24
347 #define RADIUS_SESSION_TIMEOUT 27
348 #define RADIUS_NAS_IDENTIFIER 32
349
350 /* service types : authenticate only for now */
351 #define RADIUS_AUTHENTICATE_ONLY 8
352
353 /* How large the packets may be */
354 #define RADIUS_PACKET_RECV_SIZE 1024
355 #define RADIUS_PACKET_SEND_SIZE 1024
356 #define APACHE_RADIUS_MAGIC_STATE "f36809ad"
357
358 #ifndef FALSE
359 #define FALSE 0
360 #endif
361
362 #ifndef TRUE
363 #define TRUE !FALSE
364 #endif
365
366 /* per-server configuration structure */
367 typedef struct radius_server_config_struct {
368 struct in_addr *radius_ip; /* server IP address */
369 unsigned char *secret; /* server shared secret */
370 int secret_len; /* length of the secret (to save time later) */
371 int timeout; /* cookie valid time */
372 int wait; /* wait for RADIUS server responses */
373 int retries; /* number of retries on timeout */
374 unsigned short port; /* RADIUS port number */
375 unsigned long bind_address; /* bind socket to this local address */
376 struct radius_server_config_struct *next; /* fallback server(s) */
377 } radius_server_config_rec;
378
379 /* per-server configuration create */
380 static void *
381 create_radius_server_config(pool *p, server_rec *s)
382 {
383 radius_server_config_rec *scr = (radius_server_config_rec *)
384 palloc(p, sizeof(radius_server_config_rec) );
385
386 scr->radius_ip = NULL; /* no server yet */
387 scr->port = RADIUS_AUTH_UDP_PORT; /* set the default port */
388 scr->secret = NULL; /* no secret yet */
389 scr->secret_len = 0;
390 scr->wait = 5; /* wait 5 sec before giving up on the packet */
391 scr->retries = 0; /* no additional retries */
392 scr->timeout = 60; /* valid for one hour by default */
393 scr->bind_address = INADDR_ANY;
394 scr->next = NULL;
395 return scr;
396 }
397
398 /* RADIUS utility functions */
399 static struct in_addr *
400 get_ip_addr(pool *p, char *hostname)
401 {
402 struct hostent *hp;
403
404 if ((hp = gethostbyname(hostname)) != (struct hostent *) NULL) {
405 struct in_addr *ipaddr = palloc(p, sizeof(struct in_addr));
406 *ipaddr = *(struct in_addr *) hp->h_addr; /* make a local copy */
407 return ipaddr;
408 } else {
409 return NULL;
410 }
411 }
412
413 /* get a random vector */
414 static void
415 get_random_vector(unsigned char vector[RADIUS_RANDOM_VECTOR_LEN])
416 {
417 struct timeval tv;
418 struct timezone tz;
419 static unsigned int session = 1; /* make the random number harder to guess */
420 AP_MD5_CTX my_md5;
421
422 /* Use the time of day with the best resolution the system can
423 give us -- often close to microsecond accuracy. */
424 gettimeofday(&tv,&tz);
425
426 tv.tv_sec ^= getpid() * session++; /* add some secret information: session */
427
428 /* Hash things to get some cryptographically strong pseudo-random numbers */
429 MD5Init(&my_md5);
430 MD5Update(&my_md5, (unsigned char *) &tv, sizeof(tv));
431 MD5Update(&my_md5, (unsigned char *) &tz, sizeof(tz));
432 MD5Final(vector, &my_md5); /* set the final vector */
433 }
434
435 /* Per-dir configuration structure */
436 typedef struct radius_dir_config_struct {
437 radius_server_config_rec* server;
438 int active; /* Are we doing RADIUS in this dir? */
439 int authoritative; /* is RADIUS authentication authoritative? */
440 int timeout; /* cookie time valid */
441 } radius_dir_config_rec;
442
443 /* Per-dir configuration create */
444 static void *
445 create_radius_dir_config (pool *p, char *d)
446 {
447 radius_dir_config_rec *rec =
448 (radius_dir_config_rec *) pcalloc (p, sizeof(radius_dir_config_rec));
449
450 rec->server = NULL; /* no valid server by default */
451 rec->active = 1; /* active by default */
452 rec->authoritative = 1; /* authoritative by default */
453 rec->timeout = 0; /* let the server config decide timeouts */
454 return rec;
455 }
456
457 /* per-server set configuration */
458 static const char *
459 add_auth_radius(cmd_parms *cmd, void *mconfig,
460 char *server, char *secret, char *wait)
461 {
462 radius_server_config_rec *scr;
463 unsigned int port;
464 char *p;
465
466 scr = get_module_config(cmd->server->module_config, &radius_auth_module);
467
468 /* allocate and look up the RADIUS server's IP address */
469 scr->radius_ip = palloc(cmd->pool, sizeof(struct in_addr));
470
471 /* Check to see if there's a port in the server name */
472 if ((p = strchr(server, ':')) != NULL) {
473 *(p++) = 0; /* hammer a zero in it */
474 port = atoi(p);
475 if (port < 1024) {
476 return "AddRadiusAuth: server port number must be 1024 or greater for security reasons";
477 }
478 scr->port = (unsigned short) port;
479 }
480
481 if ((scr->radius_ip = get_ip_addr(cmd->pool, server)) == NULL) {
482 return "AddRadiusAuth: Failed looking up RADIUS server IP address";
483 }
484
485 scr->secret = pstrdup(cmd->pool, secret);
486 scr->secret_len = strlen(scr->secret);
487 if (wait != NULL) {
488 if ((p = strchr(wait,':')) != NULL) {
489 *(p++) = 0; /* null terminate the wait part of the string */
490 scr->retries = atoi(p);
491 }
492 scr->wait = atoi(wait);
493 } /* else it's already initialized */
494 scr->bind_address = INADDR_ANY;
495
496 return NULL; /* everything's OK */
497 }
498
499 /*
500 * Set the local address to which this client is bound.
501 */
502 static const char *
503 set_bind_address (cmd_parms *cmd, void *mconfig, char *arg)
504 {
505 radius_server_config_rec *scr;
506 struct in_addr *a;
507
508 scr = get_module_config(cmd->server->module_config,
509 &radius_auth_module);
510 if ((a = get_ip_addr(cmd->pool, arg)) == NULL)
511 return "AuthRadiusBindAddress: invalid IP address";
512 scr->bind_address = a->s_addr;
513 return NULL;
514 }
515
516 /*
517 * Set the cookie valid time.
518 */
519 static const char *
520 set_cookie_valid(cmd_parms *cmd, void *mconfig, char *arg)
521 {
522 radius_server_config_rec *scr;
523
524 scr = get_module_config(cmd->server->module_config,
525 &radius_auth_module);
526 scr->timeout = atoi(arg);
527 return NULL; /* everything's OK */
528 }
529
530 static const char *
531 set_int_slot (cmd_parms *cmd, char *struct_ptr, char *arg)
532 {
533 int offset = (int)cmd->info;
534 *(int *)(struct_ptr + offset) = atoi(arg);
535 return NULL;
536 }
537
538 /* Table of which command does what */
539 static command_rec auth_cmds[] = {
540 { "AddRadiusAuth", add_auth_radius, NULL, RSRC_CONF, TAKE23,
541 "per-server configuration for RADIUS server name:port, shared secret, and optional timeout:retries" },
542
543 { "AuthRadiusBindAddress", set_bind_address, NULL, RSRC_CONF, TAKE1,
544 "per-server binding local socket to this local IP address. RADIUS requests will be sent *from* this IP address." },
545
546 { "AddRadiusCookieValid", set_cookie_valid, NULL, RSRC_CONF, TAKE1,
547 "per-server time in minutes for which the returned cookie is valid. After this time, authentication will be requested again. Use '0' for forever." },
548
549 { "AuthRadiusAuthoritative", set_flag_slot,
550 (void*)XtOffsetOf(radius_dir_config_rec, authoritative),
551 OR_AUTHCFG, FLAG,
552 "per-directory access on failed authentication. If set to 'no', then access control is passed along to lower modules on failed authentication." },
553
554 { "AuthRadiusCookieValid", set_int_slot,
555 (void*)XtOffsetOf(radius_dir_config_rec, timeout),
556 OR_AUTHCFG, TAKE1,
557 "per-directory time in minutes for which the returned cookie is valid. After this time, authentication will be requested again. Use '0' for forever." },
558
559 { "AuthRadiusActive", set_flag_slot,
560 (void*)XtOffsetOf(radius_dir_config_rec, active),
561 OR_AUTHCFG, FLAG,
562 "per-directory toggle the use of RADIUS authentication." },
563
564 { NULL }
565 };
566
567 static unsigned char *
568 xor(unsigned char *p, unsigned char *q, int length)
569 {
570 int i;
571 unsigned char *response = p;
572
573 for (i = 0; i < length; i++)
574 *(p++) ^= *(q++);
575 return response;
576 }
577
578 static int
579 verify_packet(request_rec *r, radius_packet_t *packet,
580 unsigned char vector[RADIUS_RANDOM_VECTOR_LEN])
581 {
582 server_rec *s = r->server;
583 radius_server_config_rec *scr = (radius_server_config_rec *)
584 get_module_config (s->module_config, &radius_auth_module);
585 AP_MD5_CTX my_md5;
586 unsigned char calculated[RADIUS_RANDOM_VECTOR_LEN];
587 unsigned char reply[RADIUS_RANDOM_VECTOR_LEN];
588
589 /*
590 * We could dispense with the memcpy, and do MD5's of the packet
591 * + vector piece by piece. This is easier understand, and probably faster.
592 */
593 memcpy(reply, packet->vector, RADIUS_RANDOM_VECTOR_LEN); /* save the reply */
594 memcpy(packet->vector, vector, RADIUS_RANDOM_VECTOR_LEN); /* sent vector */
595
596 /* MD5(packet header + vector + packet data + secret) */
597 MD5Init(&my_md5);
598 MD5Update(&my_md5, (unsigned char *) packet, ntohs(packet->length));
599 MD5Update(&my_md5, scr->secret, scr->secret_len);
600 MD5Final(calculated, &my_md5); /* set the final vector */
601
602 /* Did he use the same random vector + shared secret? */
603 if(memcmp(calculated, reply, RADIUS_RANDOM_VECTOR_LEN) != 0) {
604 return -1;
605 }
606 return 0;
607 }
608 static void
609 add_attribute(radius_packet_t *packet, int type, const unsigned char *data, int length)
610 {
611 attribute_t *p;
612
613 p = (attribute_t *) ((unsigned char *)packet + packet->length);
614 p->attribute = type;
615 p->length = length + 2; /* the total size of the attribute */
616 packet->length += p->length;
617 memcpy(p->data, data, length);
618 }
619
620 #define COOKIE_SIZE 1024
621 /* make a cookie based on secret + public information */
622 static char *
623 make_cookie(request_rec *r, time_t expires, const char *passwd, const char *string)
624 {
625 char one[COOKIE_SIZE], two[COOKIE_SIZE];
626 char *cookie = palloc(r->pool, COOKIE_SIZE);
627 conn_rec *c = r->connection;
628 server_rec *s = r->server;
629 radius_server_config_rec *scr = (radius_server_config_rec *)
630 get_module_config (s->module_config, &radius_auth_module);
631 const char *hostname;
632
633 if ((hostname = get_remote_host(c, r->per_dir_config, REMOTE_NAME)) == NULL)
634 hostname = "no.one@example.com";
635
636 /*
637 * Arg! We can't use 'ntohs(c->remote_addr.sin_port)', because I.E.
638 * ignores keepalives, and opens a new connection on EVERY request!
639 * This is a BAD security problem! It allows multiple users on the
640 * same machine to access the data.
641 *
642 * A derivative security problem is users authenticating from
643 * behind a firewall.
644 * All users appear to be coming from the firewall. A malicious
645 * agent working in the same company as the authorized user can sniff
646 * the cookie, and and use it themselves. Since they appear to be
647 * coming from the same IP address (firewall), they're let in.
648 * Oh well, at least the connection is traceable to a particular machine.
649 */
650
651 /*
652 * Piotr Klaban <makler@oryl.man.torun.pl> says:
653 *
654 * > The "squid" proxy set HTTP_X_FORWARDED_FOR variable - the
655 * > original IP of the client. We can use HTTP_X_FORWARDED_FOR
656 * > variable besides REMOTE_ADDR.
657 *
658 * > If cookie is stolen, then atacker could use the same proxy as
659 * > the client, to validate the cookie. If we would use
660 * > HTTP_X_FORWARDED_FOR, then useing the proxy would not be
661 * > sufficient.
662 *
663 * We don't do this, mainly because I haven't gotten around to
664 * writing the code...
665 */
666
667 /*
668 * Make a cookie based on secret + public information.
669 *
670 * cookie = MAC(M) = MD5(secret, MD5(secret, M))
671 *
672 * See Scheier, B, "Applied Cryptography" 2nd Ed., p.458
673 * Also, RFC 2104. I don't know if the HMAC gives any additional
674 * benefit here.
675 */
676 ap_snprintf(one, COOKIE_SIZE, "%s%s%s%s%s%08x", scr->secret,
677 c->user, passwd, c->remote_ip, hostname, expires);
678
679 /* if you're REALLY worried about what's going on */
680 #if 0
681 DPRINTF("secret = %s\n", scr->secret);
682 DPRINTF("user = %s\n", c->user);
683 DPRINTF("passwd = %s\n", passwd);
684 DPRINTF("remote ip = %s\n", c->remote_ip);
685 DPRINTF("hostname = %s\n", hostname);
686 DPRINTF("expiry = %08x\n", expires);
687 #endif
688
689 /* MD5 the cookie to make it secure, and add more secret information */
690 ap_snprintf(two, COOKIE_SIZE, "%s%s", scr->secret, ap_md5(r->pool, one));
691 if (string == NULL) {
692 ap_snprintf(cookie, COOKIE_SIZE, "%s%08x",
693 ap_md5(r->pool, two), expires);
694 } else {
695 ap_snprintf(cookie, COOKIE_SIZE, "%s%08x%s",
696 ap_md5(r->pool, two), expires, string);
697 }
698 return cookie;
699 }
700 static int
701 valid_cookie(request_rec *r, const char *cookie, const char *passwd)
702 {
703 time_t expires, now;
704
705 if (strlen(cookie) < (16 + 4)*2) { /* MD5 is 16 bytes, and expiry date is 4*/
706 return FALSE; /* invalid */
707 }
708
709 sscanf(&cookie[32], "%8lx", &expires);
710
711 now = time(NULL);
712 if (expires < now) { /* valid only for a short window of time */
713 return FALSE; /* invalid: expired */
714 }
715
716 /* Is the returned cookie identical to one made from our secret? */
717 if (strcmp(cookie, make_cookie(r, expires, passwd, NULL)) == 0)
718 return TRUE;
719
720 return FALSE; /* cookie doesn't match: re-validate */
721 }
722 /* Add a cookie to an outgoing request */
723 static const char *cookie_name = "RADIUS";
724
725 static void
726 add_cookie(request_rec *r, table *header, char *cookie, time_t expires)
727 {
728 char *new_cookie = palloc(r->pool, COOKIE_SIZE); /* so it'll stick around */
729
730 if (expires != 0) {
731 ap_snprintf(new_cookie, 1024, "%s=%s; path=/;", cookie_name, cookie);
732 } else {
733 ap_snprintf(new_cookie, 1024,
734 "%s=%s; path=/; expires=Wed, 01-Oct-97 01:01:01 GMT;",
735 cookie_name, cookie);
736 }
737
738 table_set(header,"Set-Cookie", new_cookie);
739 }
740 /* Spot a cookie in an incoming request */
741 static char *
742 spot_cookie(request_rec *r)
743 {
744 const char *cookie;
745 char *value;
746
747 if ((cookie = table_get(r->headers_in, "Cookie"))) {
748 if ((value=strstr(cookie, cookie_name))) {
749 char *cookiebuf, *cookieend;
750
751 value += strlen(cookie_name); /* skip the name */
752
753 /*
754 * Ensure there's an '=' after the name.
755 */
756 if (*value != '=') {
757 return NULL;
758 } else {
759 value++;
760 }
761
762 cookiebuf = pstrdup( r->pool, value );
763 cookieend = strchr(cookiebuf,';');
764 if (cookieend) *cookieend = '\0'; /* Ignore anything after a ; */
765
766 /* Set the cookie in a note, for logging */
767 return cookiebuf; /* Theres already a cookie, no new one */
768 }
769 }
770 return NULL; /* no cookie was found */
771 }
772
773 /* There's a lot of parameters to this function, but it does a lot of work */
774 static int
775 radius_authenticate(request_rec *r, radius_server_config_rec *scr,
776 int sockfd, int code, char *recv_buffer,
777 const char *user, const char *passwd_in, const char *state,
778 unsigned char *vector, char *errstr)
779 {
780 struct sockaddr_in *sin;
781 struct sockaddr saremote;
782 int salen, total_length;
783 fd_set set;
784 int retries = scr->retries;
785 struct timeval tv;
786 int rcode;
787 struct in_addr *ip_addr;
788
789 unsigned char misc[RADIUS_RANDOM_VECTOR_LEN];
790 int password_len, i;
791 unsigned char password[128];
792 AP_MD5_CTX md5_secret, my_md5;
793 UINT4 service;
794
795 unsigned char send_buffer[RADIUS_PACKET_SEND_SIZE];
796 radius_packet_t *packet = (radius_packet_t *) send_buffer;
797
798 i = strlen(passwd_in);
799 password_len = (i + 0x0f) & 0xfffffff0; /* round off to 16 */
800 if (password_len == 0) {
801 password_len = 16; /* it's at least 15 bytes long */
802 } else if (password_len > 128) { /* password too long, from RFC2138, p.22 */
803 ap_snprintf(errstr, MAX_STRING_LEN, "password given by user %s is too long for RADIUS", user);
804 return FALSE;
805 }
806
807 memset(password, 0, password_len);
808 memcpy(password, passwd_in, i); /* don't use strcpy! */
809
810 /* ************************************************************ */
811 /* generate a random authentication vector */
812 get_random_vector(vector);
813
814 /* ************************************************************ */
815 /* Fill in the packet header */
816 memset(send_buffer, 0, sizeof(send_buffer));
817
818 packet->code = code;
819 packet->id = vector[0]; /* make a random request id */
820 packet->length = RADIUS_HEADER_LEN;
821 memcpy(packet->vector, vector, RADIUS_RANDOM_VECTOR_LEN);
822
823 /* Fill in the user name attribute */
824 add_attribute(packet, RADIUS_USER_NAME, user, strlen(user));
825
826 /* ************************************************************ */
827 /* encrypt the password */
828 /* password : e[0] = p[0] ^ MD5(secret + vector) */
829 MD5Init(&md5_secret);
830 MD5Update(&md5_secret, scr->secret, scr->secret_len);
831 my_md5 = md5_secret; /* so we won't re-do the hash later */
832 MD5Update(&my_md5, vector, RADIUS_RANDOM_VECTOR_LEN);
833 MD5Final(misc, &my_md5); /* set the final vector */
834 xor(password, misc, RADIUS_PASSWORD_LEN);
835
836 /* For each step through, e[i] = p[i] ^ MD5(secret + e[i-1]) */
837 for (i = 1; i < (password_len >> 4); i++) {
838 my_md5 = md5_secret; /* grab old value of the hash */
839 MD5Update(&my_md5, &password[(i-1) * RADIUS_PASSWORD_LEN], RADIUS_PASSWORD_LEN);
840 MD5Final(misc, &my_md5); /* set the final vector */
841 xor(&password[i * RADIUS_PASSWORD_LEN], misc, RADIUS_PASSWORD_LEN);
842 }
843 add_attribute(packet, RADIUS_PASSWORD, password, password_len);
844
845 /* ************************************************************ */
846 /* Tell the RADIUS server that we only want to authenticate */
847 service = htonl(RADIUS_AUTHENTICATE_ONLY);
848 add_attribute(packet, RADIUS_SERVICE_TYPE, (unsigned char *) &service,
849 sizeof(service));
850
851 /* ************************************************************ */
852 /* Tell the RADIUS server which virtual server we're coming from */
853 add_attribute(packet, RADIUS_NAS_IDENTIFIER, r->server->server_hostname,
854 strlen(r->server->server_hostname));
855
856 /* ************************************************************ */
857 /* Tell the RADIUS server which IP address we're coming from */
858 if (scr->radius_ip->s_addr == htonl(0x7f000001)) {
859 ip_addr = scr->radius_ip; /* go to localhost through localhost */
860 } else {
861 ip_addr = get_ip_addr(r->pool, r->connection->base_server->server_hostname);
862 if (ip_addr == NULL) {
863 ap_snprintf(errstr, MAX_STRING_LEN, "cannot look up server hostname %s",
864 r->connection->base_server->server_hostname);
865 return FALSE;
866 }
867 }
868
869 add_attribute(packet, RADIUS_NAS_IP_ADDRESS, (unsigned char *)&ip_addr->s_addr,
870 sizeof(ip_addr->s_addr));
871
872
873 /* ************************************************************ */
874 /* add state, if requested */
875 if (state != NULL) {
876 add_attribute(packet, RADIUS_STATE, state, strlen(state));
877 }
878
879 /* ************************************************************ */
880 /* Now that we're done building the packet, we can send it */
881 total_length = packet->length;
882 packet->length = htons(packet->length);
883
884 sin = (struct sockaddr_in *) &saremote;
885 memset ((char *) sin, '\0', sizeof(saremote));
886 sin->sin_family = AF_INET;
887 sin->sin_addr.s_addr = scr->radius_ip->s_addr;
888 sin->sin_port = htons(scr->port);
889
890 while (retries >= 0) {
891 if (sendto(sockfd, (char *) packet, total_length, 0,
892 &saremote, sizeof(struct sockaddr_in)) < 0) {
893 ap_snprintf(errstr, MAX_STRING_LEN, "error sending RADIUS packet for user %s: %s", user, strerror(errno));
894 return FALSE;
895 }
896
897 wait_again:
898 /* ************************************************************ */
899 /* Wait for the response, and verify it. */
900 salen = sizeof (saremote);
901 tv.tv_sec = scr->wait; /* wait for the specified time */
902 tv.tv_usec = 0;
903 FD_ZERO(&set); /* clear out the set */
904 FD_SET(sockfd, &set); /* wait only for the RADIUS UDP socket */
905
906 rcode = select(sockfd + 1, &set, NULL, NULL, &tv);
907 if ((rcode < 0) && (errno == EINTR)) {
908 goto wait_again; /* signal, ignore it */
909 }
910
911 if (rcode == 0) { /* done the select, with no data ready */
912 retries--;
913 } else {
914 break; /* exit from the 'while retries' loop */
915 }
916 } /* loop over the retries */
917
918 /*
919 * Error. Die.
920 */
921 if (rcode < 0) {
922 ap_snprintf(errstr, MAX_STRING_LEN, "error waiting for RADIUS response: %s", strerror(errno));
923 return FALSE;
924 }
925
926 /*
927 * Time out.
928 */
929 if (rcode == 0) {
930 ap_snprintf(errstr, MAX_STRING_LEN, "RADIUS server %s failed to respond within %d seconds after each of %d retries",
931 inet_ntoa(*scr->radius_ip), scr->wait, scr->retries);
932 return FALSE;
933 }
934
935 if ((total_length = recvfrom(sockfd, (char *) recv_buffer,
936 RADIUS_PACKET_RECV_SIZE,
937 0, &saremote, &salen)) < 0) {
938 ap_snprintf(errstr, MAX_STRING_LEN, "error reading RADIUS packet: %s", strerror(errno));
939 return FALSE;
940 } else {
941
942 packet = (radius_packet_t *) recv_buffer; /* we have a new packet */
943 if ((ntohs(packet->length) > total_length) ||
944 (ntohs(packet->length) > RADIUS_PACKET_RECV_SIZE)) {
945 ap_snprintf(errstr, MAX_STRING_LEN, "RADIUS packet corrupted");
946 return FALSE;
947 }
948 }
949
950 /* Check if we've got everything OK. We should also check packet->id...*/
951 if (verify_packet(r, packet, vector)) {
952 ap_snprintf(errstr, MAX_STRING_LEN, "RADIUS packet fails verification");
953 return FALSE;
954 }
955
956 return TRUE;
957 }
958
959 /* Find a particular attribute. All we really care about is STATE */
960 static attribute_t *
961 find_attribute(radius_packet_t *packet, unsigned char type)
962 {
963 attribute_t *attr = &packet->first;
964 int len = ntohs(packet->length) - RADIUS_HEADER_LEN;
965
966 while (attr->attribute != type) {
967 if ((len -= attr->length) <= 0) {
968 return NULL; /* not found */
969 }
970 attr = (attribute_t *) ((char *) attr + attr->length);
971 }
972 return attr;
973 }
974 #define radcpy(STRING, ATTR) {memcpy(STRING, ATTR->data, ATTR->length - 2); \
975 (STRING)[ATTR->length - 2] = 0;}
976
977
978 /* authentication module utility functions */
979 static int
980 check_pw(request_rec *r, radius_server_config_rec *scr, const char *user, const char *passwd_in, const char *state, char *message, char *errstr)
981 {
982 struct sockaddr_in *sin;
983 struct sockaddr salocal;
984 int sockfd;
985 unsigned short local_port;
986
987 unsigned char vector[RADIUS_RANDOM_VECTOR_LEN];
988 unsigned char recv_buffer[RADIUS_PACKET_RECV_SIZE];
989 radius_packet_t *packet;
990
991 int rcode;
992
993 /* ************************************************************ */
994 /* connect to a port */
995 if ((sockfd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) {
996 ap_snprintf(errstr, MAX_STRING_LEN, "error opening RADIUS socket for user %s: %s", user, strerror(errno));
997 return FALSE;
998 }
999
1000 sin = (struct sockaddr_in *) &salocal;
1001 memset((char *) sin, '\0', sizeof(salocal));
1002 sin->sin_family = AF_INET;
1003 sin->sin_addr.s_addr = scr->bind_address;
1004
1005 local_port = 1025;
1006 do {
1007 local_port++;
1008 sin->sin_port = htons((unsigned short) local_port);
1009 } while((bind(sockfd, &salocal, sizeof(struct sockaddr_in)) < 0) &&
1010 (local_port < 64000));
1011 if(local_port >= 64000) {
1012 close(sockfd);
1013 ap_snprintf(errstr, MAX_STRING_LEN, "cannot bind to RADIUS socket for user %s", user);
1014 return FALSE;
1015 }
1016
1017 rcode = radius_authenticate(r, scr, sockfd, RADIUS_ACCESS_REQUEST, recv_buffer, user, passwd_in, state, vector, errstr);
1018
1019 close(sockfd); /* we're done with it */
1020
1021 if (rcode == FALSE) {
1022 return FALSE; /* error out */
1023 }
1024
1025 packet = (radius_packet_t *) recv_buffer;
1026
1027 switch (packet->code)
1028 {
1029
1030 case RADIUS_ACCESS_ACCEPT:
1031 {
1032 attribute_t *a_timeout;
1033 int i;
1034
1035 a_timeout = find_attribute(packet, RADIUS_SESSION_TIMEOUT);
1036 if (a_timeout) {
1037 memcpy(&i, a_timeout->data, 4);
1038 i = ntohl(i);
1039 }
1040 }
1041 *message = 0; /* no message */
1042 return TRUE; /* he likes you! */
1043 break;
1044
1045 case RADIUS_ACCESS_REJECT:
1046 ap_snprintf(errstr, MAX_STRING_LEN, "RADIUS authentication failed for user %s", user);
1047 break;
1048
1049 case RADIUS_ACCESS_CHALLENGE:
1050 {
1051 attribute_t *a_state, *a_reply;
1052 time_t expires = time(NULL) + 120; /* state expires in two minutes */
1053 char server_state[256];
1054
1055 if (((a_state = find_attribute(packet, RADIUS_STATE)) == NULL) ||
1056 ((a_reply = find_attribute(packet, RADIUS_REPLY_MESSAGE)) == NULL)) {
1057 ap_snprintf(errstr, MAX_STRING_LEN, "RADIUS access-challenge received with State or Reply-Message missing");
1058 } else {
1059 char *p;
1060
1061 /* Copy magic state message to the state */
1062 strcpy(server_state, APACHE_RADIUS_MAGIC_STATE);
1063 radcpy(server_state + sizeof(APACHE_RADIUS_MAGIC_STATE) - 1,
1064 a_state);
1065
1066 /* Copy the Reply-Message back to the caller : do CR/LF smashing */
1067 radcpy(message, a_reply);
1068
1069 p = message; /* strip any control characters */
1070 while (*p) {
1071 if (*p < ' ')
1072 *p = ' ';
1073 p++;
1074 }
1075
1076 /* set the magic cookie */
1077 add_cookie(r, r->err_headers_out,
1078 make_cookie(r, expires, "", server_state), expires);
1079
1080 /* log the challenge, as it IS an error returned to the user */
1081 ap_snprintf(errstr, MAX_STRING_LEN,
1082 "RADIUS server requested challenge for user %s", user);
1083
1084 }
1085 }
1086 break;
1087
1088 default: /* don't know what else to do */
1089 ap_snprintf(errstr, MAX_STRING_LEN,
1090 "RADIUS server returned unknown response %02x",
1091 packet->code);
1092 break;
1093 }
1094
1095 return FALSE; /* default to failing authentication */
1096 }
1097
1098 void
1099 note_challenge_auth_failure(request_rec *r, char *user, char *message)
1100 {
1101 if (!*message) { /* no message to print */
1102 note_basic_auth_failure(r);
1103 } else { /* print our magic message */
1104 table_set (r->err_headers_out, "WWW-Authenticate",
1105 pstrcat(r->pool, "Basic realm=\"", auth_name(r), " for ", user, " '", message, "'", NULL));
1106 }
1107 }
1108 /* These functions return 0 if client is OK, and proper error status
1109 * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
1110 * SERVER_ERROR, if things are so totally confused that we couldn't
1111 * figure out how to tell if the client is authorized or not.
1112 *
1113 * If they return DECLINED, and all other modules also decline, that's
1114 * treated by the server core as a configuration error, logged and
1115 * reported as such.
1116 */
1117
1118 /* Determine user ID, and check if it really is that user, for HTTP
1119 * basic authentication...
1120 */
1121
1122 static int
1123 authenticate_basic_user(request_rec *r)
1124 {
1125 radius_dir_config_rec *rec =
1126 (radius_dir_config_rec *)get_module_config (r->per_dir_config, &radius_auth_module);
1127 server_rec *s = r->server;
1128 radius_server_config_rec *scr = (radius_server_config_rec *)
1129 get_module_config (s->module_config, &radius_auth_module);
1130 conn_rec *c = r->connection;
1131 const char *sent_pw;
1132 char errstr[MAX_STRING_LEN];
1133 int res, min;
1134 char *cookie;
1135 char *state = NULL;
1136 char message[256];
1137 time_t expires;
1138 struct stat buf;
1139
1140 if (!rec->active || !scr->radius_ip) /* not active here, or no radius */
1141 return DECLINED; /* server declared, decline */
1142
1143 if ((res = get_basic_auth_pw(r, &sent_pw)))
1144 return res;
1145
1146 if (c->user[0] == 0) /* NUL users can never be let in */
1147 return HTTP_UNAUTHORIZED;
1148
1149 message[0] = 0; /* no message for now */
1150
1151 DPRINTF("###### %s requests %s : file=%s ######\n",
1152 r->server->server_hostname, r->uri, r->filename);
1153
1154 /* check for the existence of a cookie: do weak authentication if so */
1155 if ((cookie = spot_cookie(r)) != NULL) {
1156 DPRINTF("Found cookie=%s for user=%s : ", cookie, c->user);
1157 /* are we in a Challenge-Response intermediate state? */
1158 if (((state = strstr(cookie, APACHE_RADIUS_MAGIC_STATE)) != NULL) &&
1159 ((state - cookie) == 40)) { /* it's in the right place */
1160 DPRINTF("with RADIUS challenge state set.\n");
1161 /*
1162 * If there's an authentication failure, ensure we delete the state.
1163 * If authentication succeeds, the new cookie will supersede the old.
1164 * (RFC 2109, 4.3.3)
1165 */
1166 add_cookie(r, r->err_headers_out, cookie, 0);
1167 state += sizeof(APACHE_RADIUS_MAGIC_STATE) -1; /* skip state string */
1168
1169 /* valid username, passwd, and expiry date: don't do RADIUS */
1170 } else if (valid_cookie(r, cookie, sent_pw)) {
1171 DPRINTF("still valid. Serving page.\n");
1172 return OK;
1173 } else { /* the cookie has probably expired */
1174 /* don't bother logging the fact: we probably don't care */
1175 add_cookie(r, r->err_headers_out, cookie, 0);
1176 note_challenge_auth_failure(r, c->user, message);
1177 DPRINTF("invalid or expired. telling browser to delete cookie\n");
1178 return AUTH_REQUIRED;
1179 }
1180 } else {
1181 DPRINTF("No cookie found. Trying RADIUS authentication.\n");
1182 }
1183
1184 /*
1185 * This is for one-time passwords, so we don't get too badly out of sync .
1186 * Also, don't bother doing the stat for requests we're proxying.
1187 */
1188 if ((strstr(r->filename, "proxy:") != r->filename) &&
1189 (stat(r->filename, &buf) < 0)) {
1190 return HTTP_NOT_FOUND; /* can't stat it, so we can't authenticate it */
1191 }
1192
1193 /* Check the password, and fill in the error string if an error happens */
1194 if (!(check_pw(r, scr, c->user, sent_pw, state, message, errstr))) {
1195 DPRINTF("RADIUS authentication for user=%s password=%s failed\n",
1196 c->user, sent_pw);
1197 if (!(rec->authoritative)) {
1198 DPRINTF("We're not authoritative. Never mind.\n");
1199 return DECLINED; /* never mind */
1200 }
1201 log_reason (errstr, r->uri, r);
1202 note_challenge_auth_failure(r, c->user, message);
1203 DPRINTF("Sending failure message to user=%s : '%s'\n", c->user, message);
1204 return AUTH_REQUIRED;
1205 }
1206
1207 min = scr->timeout; /* the server config is authoritative */
1208 if (scr->timeout == 0) { /* except that zero means forever */
1209 min = 24*30*60; /* expire in one month (that's forever!) */
1210 }
1211
1212 if ((rec->timeout != 0) && /* if we don't let the server choose */
1213 (rec->timeout < min)) { /* and we're more restrictive than the server */
1214 min = rec->timeout; /* use the directory config */
1215 }
1216
1217 expires = time(NULL) + (min * 60);
1218 cookie = make_cookie(r, expires, sent_pw, NULL);
1219
1220 DPRINTF("RADIUS Authentication for user=%s password=%s OK. Cookie expiry in %d minutes\n",
1221 c->user, sent_pw, min);
1222 DPRINTF("Adding cookie %s\n", cookie);
1223
1224 add_cookie(r, r->headers_out, cookie, expires);
1225 return OK;
1226 }
1227
1228 module radius_auth_module = {
1229 STANDARD_MODULE_STUFF,
1230 NULL, /* initializer */
1231 create_radius_dir_config, /* dir config creater */
1232 NULL, /* dir merger --- default is to override */
1233 create_radius_server_config, /* server config */
1234 NULL, /* merge server config */
1235 auth_cmds, /* command table */
1236 NULL, /* handlers */
1237 NULL, /* filename translation */
1238 authenticate_basic_user, /* check_user_id */
1239 NULL, /* check auth */
1240 NULL, /* check access */
1241 NULL, /* type_checker */
1242 NULL, /* fixups */
1243 NULL, /* logger */
1244 NULL /* header parser */
1245 };