61ca6ea3aacfe619895a12bfb14431ec000cfdf5
[exim.git] / src / src / miscmods / radius.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2016 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* This file was originally supplied by Ian Kirk. The libradius support came
11 from Alex Kiernan. */
12
13 #include "../exim.h"
14
15 /* This module contains functions that call the Radius authentication
16 mechanism.
17
18 We can't just compile this code and allow the library mechanism to omit the
19 functions if they are not wanted, because we need to have the Radius headers
20 available for compiling. Therefore, compile these functions only if
21 RADIUS_CONFIG_FILE is defined. However, some compilers don't like compiling
22 empty modules, so keep them happy with a dummy when skipping the rest. Make it
23 reference itself to stop picky compilers complaining that it is unused, and put
24 in a dummy argument to stop even pickier compilers complaining about infinite
25 loops. Then use a mutually-recursive pair as gcc is just getting stupid. */
26
27 #ifndef RADIUS_CONFIG_FILE
28 static void dummy(int x);
29 static void dummy2(int x) { dummy(x-1); }
30 static void dummy(int x) { dummy2(x-1); }
31 #else  /* RADIUS_CONFIG_FILE */
32
33
34 /* Two different Radius libraries are supported. The default is radiusclient,
35 using its original API. At release 0.4.0 the API changed. */
36
37 #ifdef RADIUS_LIB_RADLIB
38 # include <radlib.h>
39 #else
40 # if !defined(RADIUS_LIB_RADIUSCLIENT) && !defined(RADIUS_LIB_RADIUSCLIENTNEW)
41 #  define RADIUS_LIB_RADIUSCLIENT
42 # endif
43
44 # ifdef RADIUS_LIB_RADIUSCLIENTNEW
45 #  define ENV FREERADIUSCLIENT_ENV      /* Avoid clash with Berkeley DB */
46 #  include <freeradius-client.h>
47 # else
48 #  include <radiusclient.h>
49 # endif
50 #endif
51
52
53
54 /*************************************************
55 *              Perform RADIUS authentication     *
56 *************************************************/
57
58 /* This function calls the Radius authentication mechanism, passing over one or
59 more data strings.
60
61 Arguments:
62   s        a colon-separated list of strings
63   errptr   where to point an error message
64
65 Returns:   OK if authentication succeeded
66            FAIL if authentication failed
67            ERROR some other error condition
68 */
69
70 static int
71 auth_call_radius(const uschar *s, uschar **errptr)
72 {
73 uschar *user;
74 const uschar *radius_args = s;
75 int result;
76 int sep = 0;
77
78 #ifdef RADIUS_LIB_RADLIB
79   struct rad_handle *h;
80 #else
81   #ifdef RADIUS_LIB_RADIUSCLIENTNEW
82     rc_handle *h;
83   #endif
84   VALUE_PAIR *send = NULL;
85   VALUE_PAIR *received;
86   unsigned int service = PW_AUTHENTICATE_ONLY;
87   char msg[4096];
88 #endif
89
90
91 if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US"";
92
93 DEBUG(D_auth) debug_printf("Running RADIUS authentication for user \"%s\" "
94                "and \"%s\"\n", user, radius_args);
95
96 *errptr = NULL;
97
98
99 /* Authenticate using the radiusclient library */
100
101 #ifndef RADIUS_LIB_RADLIB
102
103 rc_openlog("exim");
104
105 #ifdef RADIUS_LIB_RADIUSCLIENT
106 if (rc_read_config(RADIUS_CONFIG_FILE) != 0)
107   *errptr = string_sprintf("RADIUS: can't open %s", RADIUS_CONFIG_FILE);
108
109 else if (rc_read_dictionary(rc_conf_str("dictionary")) != 0)
110   *errptr = US"RADIUS: can't read dictionary";
111
112 else if (!rc_avpair_add(&send, PW_USER_NAME, user, 0))
113   *errptr = US"RADIUS: add user name failed";
114
115 else if (!rc_avpair_add(&send, PW_USER_PASSWORD, CS radius_args, 0))
116   *errptr = US"RADIUS: add password failed");
117
118 else if (!rc_avpair_add(&send, PW_SERVICE_TYPE, &service, 0))
119   *errptr = US"RADIUS: add service type failed";
120
121 #else  /* RADIUS_LIB_RADIUSCLIENT unset => RADIUS_LIB_RADIUSCLIENT2 */
122
123 if (!(h = rc_read_config(RADIUS_CONFIG_FILE)))
124   *errptr = string_sprintf("RADIUS: can't open %s", RADIUS_CONFIG_FILE);
125
126 else if (rc_read_dictionary(h, rc_conf_str(h, "dictionary")) != 0)
127   *errptr = US"RADIUS: can't read dictionary";
128
129 else if (!rc_avpair_add(h, &send, PW_USER_NAME, user, Ustrlen(user), 0))
130   *errptr = US"RADIUS: add user name failed";
131
132 else if (!rc_avpair_add(h, &send, PW_USER_PASSWORD, CS radius_args,
133     Ustrlen(radius_args), 0))
134   *errptr = US"RADIUS: add password failed";
135
136 else if (!rc_avpair_add(h, &send, PW_SERVICE_TYPE, &service, 0, 0))
137   *errptr = US"RADIUS: add service type failed";
138
139 #endif  /* RADIUS_LIB_RADIUSCLIENT */
140
141 if (*errptr)
142   {
143   DEBUG(D_auth) debug_printf("%s\n", *errptr);
144   return ERROR;
145   }
146
147 #ifdef RADIUS_LIB_RADIUSCLIENT
148 result = rc_auth(0, send, &received, msg);
149 #else
150 result = rc_auth(h, 0, send, &received, msg);
151 #endif
152
153 DEBUG(D_auth) debug_printf("RADIUS code returned %d\n", result);
154
155 switch (result)
156   {
157   case OK_RC:
158     return OK;
159
160   case REJECT_RC:
161   case ERROR_RC:
162     return FAIL;
163
164   case TIMEOUT_RC:
165     *errptr = US"RADIUS: timed out";
166     return ERROR;
167
168   case BADRESP_RC:
169   default:
170     *errptr = string_sprintf("RADIUS: unexpected response (%d)", result);
171     return ERROR;
172   }
173
174 #else  /* RADIUS_LIB_RADLIB is set */
175
176 /* Authenticate using the libradius library */
177
178 if (!(h = rad_auth_open()))
179   {
180   *errptr = string_sprintf("RADIUS: can't initialise libradius");
181   return ERROR;
182   }
183 if (rad_config(h, RADIUS_CONFIG_FILE) != 0 ||
184     rad_create_request(h, RAD_ACCESS_REQUEST) != 0 ||
185     rad_put_string(h, RAD_USER_NAME, CS user) != 0 ||
186     rad_put_string(h, RAD_USER_PASSWORD, CS radius_args) != 0 ||
187     rad_put_int(h, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) != 0 ||
188     rad_put_string(h, RAD_NAS_IDENTIFIER, CS primary_hostname) != 0)
189   {
190   *errptr = string_sprintf("RADIUS: %s", rad_strerror(h));
191   result = ERROR;
192   }
193 else
194   switch (result = rad_send_request(h))
195     {
196     case RAD_ACCESS_ACCEPT:
197       result = OK;
198       break;
199
200     case RAD_ACCESS_REJECT:
201       result = FAIL;
202       break;
203
204     case -1:
205       *errptr = string_sprintf("RADIUS: %s", rad_strerror(h));
206       result = ERROR;
207       break;
208
209     default:
210       *errptr = string_sprintf("RADIUS: unexpected response (%d)", result);
211       result= ERROR;
212       break;
213     }
214
215 if (*errptr) DEBUG(D_auth) debug_printf("%s\n", *errptr);
216 rad_close(h);
217 return result;
218
219 #endif  /* RADIUS_LIB_RADLIB */
220 }
221
222
223
224 /******************************************************************************/
225 /* Module API */
226
227 static void * rad_functions[] = {
228   [RADIUS_AUTH_CALL] =  auth_call_radius,
229 };
230
231 misc_module_info rad_module_info =
232 {
233   .name =               US"radius",
234 # ifdef DYNLOOKUP
235   .dyn_magic =          MISC_MODULE_MAGIC,
236 # endif
237
238   .functions =          rad_functions,
239   .functions_count =    nelem(rad_functions),
240 };
241
242 #endif  /* RADIUS_CONFIG_FILE */
243
244 /* End of call_radius.c */