1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 2005-2021 Free Software Foundation, Inc.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 3 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General
15 Public License along with this library. If not, see
16 <http://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <pwd.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #ifdef HAVE_STRINGS_H
30 # include <strings.h>
31 #endif
32
33 #include <mailutils/list.h>
34 #include <mailutils/iterator.h>
35 #include <mailutils/mailbox.h>
36 #include <mailutils/radius.h>
37 #include <mailutils/cstr.h>
38 #include <mailutils/wordsplit.h>
39 #include <mailutils/mu_auth.h>
40 #include <mailutils/error.h>
41 #include <mailutils/errno.h>
42 #include <mailutils/nls.h>
43 #include <mailutils/io.h>
44 #include <mailutils/cctype.h>
45 #include <mailutils/cli.h>
46 #include <mailutils/stream.h>
47 #include <mailutils/stdstream.h>
48
49 #ifdef ENABLE_RADIUS
50
51 #include <radius/radius.h>
52 #include <radius/debug.h>
53
54 static int radius_auth_enabled;
55
56 static int MU_User_Name;
57 static int MU_UID;
58 static int MU_GID;
59 static int MU_GECOS;
60 static int MU_Dir;
61 static int MU_Shell;
62 static int MU_Mailbox;
63
64 static grad_avp_t *auth_request;
65 static grad_avp_t *getpwnam_request;
66 static grad_avp_t *getpwuid_request;
67
68 int
get_attribute(int * pattr,char * name)69 get_attribute (int *pattr, char *name)
70 {
71 grad_dict_attr_t *attr = grad_attr_name_to_dict (name);
72 if (!attr)
73 {
74 mu_error (_("RADIUS attribute %s not defined"), name);
75 return 1;
76 }
77 *pattr = attr->value;
78 return 0;
79 }
80
81 enum parse_state
82 {
83 state_lhs,
84 state_op,
85 state_rhs,
86 state_delim,
87 state_err
88 };
89
90 static int
cb_request(void * data,mu_config_value_t * val)91 cb_request (void *data, mu_config_value_t *val)
92 {
93 grad_avp_t **plist = data;
94 size_t i;
95 struct mu_wordsplit ws;
96 enum parse_state state;
97 grad_locus_t loc;
98 char *name;
99 struct mu_locus_range locus;
100
101 if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
102 return 1;
103
104 ws.ws_delim = ",";
105 if (mu_wordsplit (val->v.string, &ws,
106 MU_WRDSF_DEFFLAGS|MU_WRDSF_DELIM|MU_WRDSF_RETURN_DELIMS))
107 {
108 mu_error (_("cannot parse input `%s': %s"), val->v.string,
109 mu_wordsplit_strerror (&ws));
110 return 1;
111 }
112
113 if (mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
114 MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE,
115 &locus) == 0)
116 {
117 loc.file = (char*) locus.beg.mu_file;
118 loc.line = locus.beg.mu_line;
119 }
120 else
121 {
122 loc.file = "<unknown>";
123 loc.line = 0;
124 }
125
126 for (i = 0, state = state_lhs; state != state_err && i < ws.ws_wordc; i++)
127 {
128 grad_avp_t *pair;
129
130 switch (state)
131 {
132 case state_lhs:
133 name = ws.ws_wordv[i];
134 state = state_op;
135 break;
136
137 case state_op:
138 //op = ws.ws_wordv[i];
139 state = state_rhs;
140 break;
141
142 case state_rhs:
143 loc.line = i; /* Just to keep track of error location */
144 pair = grad_create_pair (&loc, name, grad_operator_equal,
145 ws.ws_wordv[i]);
146 if (!pair)
147 {
148 mu_error (_("cannot create radius A/V pair `%s'"), name);
149 state = state_err;
150 }
151 else
152 {
153 grad_avl_merge (plist, &pair);
154 state = state_delim;
155 }
156 break;
157
158 case state_delim:
159 if (strcmp (ws.ws_wordv[i], ","))
160 {
161 mu_error (_("expected `,' but found `%s'"), ws.ws_wordv[i]);
162 state = state_err;
163 }
164 else
165 state = state_lhs;
166 break;
167
168 default:
169 abort ();
170 }
171 }
172
173 mu_wordsplit_free (&ws);
174 mu_locus_range_deinit (&locus);
175
176 if (state == state_err)
177 return 1;
178
179 if (state != state_delim && state != state_delim)
180 {
181 mu_error (_("malformed radius A/V list"));
182 return 1;
183 }
184
185 return 0;
186 }
187
188 static int
cb_config_dir(void * data,mu_config_value_t * val)189 cb_config_dir (void *data, mu_config_value_t *val)
190 {
191 if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
192 return 1;
193 grad_config_dir = grad_estrdup (val->v.string);
194 return 0;
195 }
196
197 static struct mu_cfg_param mu_radius_param[] = {
198 { "auth", mu_cfg_callback, &auth_request, 0, cb_request,
199 N_("Radius request for authorization."),
200 N_("request: string") },
201 { "getpwnam", mu_cfg_callback, &getpwnam_request, 0, cb_request,
202 N_("Radius request for getpwnam."),
203 N_("request: string") },
204 { "getpwuid", mu_cfg_callback, &getpwuid_request, 0, cb_request,
205 N_("Radius request for getpwuid."),
206 N_("request: string") },
207 { "directory", mu_cfg_callback, NULL, 0, cb_config_dir,
208 N_("Set radius configuration directory."),
209 N_("dir: string") },
210 { NULL }
211 };
212
213 struct mu_cli_capa mu_cli_capa_radius = {
214 "radius",
215 NULL,
216 mu_radius_param
217 };
218
219 /* Assume radius support is needed if any of the above requests is
220 defined. Actually, all of them should be, but it is the responsibility
221 of init to check for consistency of the configuration */
222
223 #define NEED_RADIUS_P() \
224 (auth_request || getpwnam_request || getpwuid_request)
225
226 static void
mu_grad_logger(int level,const grad_request_t * req,const grad_locus_t * loc,const char * func_name,int en,const char * fmt,va_list ap)227 mu_grad_logger(int level,
228 const grad_request_t *req,
229 const grad_locus_t *loc,
230 const char *func_name, int en,
231 const char *fmt, va_list ap)
232 {
233 static int mlevel[] = {
234 MU_DIAG_EMERG,
235 MU_DIAG_ALERT,
236 MU_DIAG_CRIT,
237 MU_DIAG_ERROR,
238 MU_DIAG_WARNING,
239 MU_DIAG_NOTICE,
240 MU_DIAG_INFO,
241 MU_DIAG_DEBUG
242 };
243
244 char *pfx = NULL;
245 if (loc)
246 {
247 if (func_name)
248 mu_asprintf (&pfx, "%s:%lu:%s: %s",
249 loc->file, (unsigned long) loc->line, func_name, fmt);
250 else
251 mu_asprintf (&pfx, "%s:%lu: %s",
252 loc->file, (unsigned long) loc->line, fmt);
253 }
254 mu_diag_voutput (mlevel[level & GRAD_LOG_PRIMASK], pfx ? pfx : fmt, ap);
255 if (pfx)
256 free (pfx);
257 }
258
259 static void
module_init(void * ptr)260 module_init (void *ptr)
261 {
262 if (!NEED_RADIUS_P ())
263 return;
264
265 grad_set_logger (mu_grad_logger);
266
267 grad_path_init ();
268 srand (time (NULL) + getpid ());
269
270 if (grad_dict_init ())
271 {
272 mu_error (_("cannot read radius dictionaries"));
273 return;
274 }
275
276 /* Check whether mailutils attributes are defined */
277 if (get_attribute (&MU_User_Name, "MU-User-Name")
278 || get_attribute (&MU_UID, "MU-UID")
279 || get_attribute (&MU_GID, "MU-GID")
280 || get_attribute (&MU_GECOS, "MU-GECOS")
281 || get_attribute (&MU_Dir, "MU-Dir")
282 || get_attribute (&MU_Shell, "MU-Shell")
283 || get_attribute (&MU_Mailbox, "MU-Mailbox"))
284 return;
285
286 radius_auth_enabled = 1;
287 }
288
289 static char *
_expand_query(const char * query,const char * ustr,const char * passwd)290 _expand_query (const char *query, const char *ustr, const char *passwd)
291 {
292 char *ret;
293 int rc;
294
295 rc = mu_str_vexpand (&ret, query,
296 "user", ustr,
297 "passwd", passwd,
298 NULL);
299 if (rc)
300 {
301 if (rc == MU_ERR_FAILURE)
302 {
303 mu_error (_("cannot expand line `%s': %s"), query, ret);
304 free (ret);
305 }
306 else
307 mu_error (_("cannot expand line `%s': %s"), query, mu_strerror (rc));
308 return NULL;
309 }
310
311 return ret;
312 }
313
314
315 static grad_avp_t *
create_request(grad_avp_t * template,const char * ustr,const char * passwd)316 create_request (grad_avp_t *template, const char *ustr, const char *passwd)
317 {
318 grad_avp_t *newp, *p;
319
320 newp = grad_avl_dup (template);
321 for (p = newp; p; p = p->next)
322 {
323 if (p->type == GRAD_TYPE_STRING)
324 {
325 char *value = _expand_query (p->avp_strvalue, ustr, passwd);
326 grad_free (p->avp_strvalue);
327 p->avp_strvalue = value;
328 p->avp_strlength = strlen (value);
329 }
330 }
331 return newp;
332 }
333
334
335
336 grad_request_t *
send_request(grad_avp_t * pairs,int code,const char * user,const char * passwd)337 send_request (grad_avp_t *pairs, int code,
338 const char *user, const char *passwd)
339 {
340 grad_avp_t *plist = create_request (pairs, user, passwd);
341 if (plist)
342 {
343 grad_server_queue_t *queue = grad_client_create_queue (1, 0, 0);
344 grad_request_t *reply = grad_client_send (queue,
345 GRAD_PORT_AUTH, code,
346 plist);
347 grad_client_destroy_queue (queue);
348 grad_avl_free (plist);
349 return reply;
350 }
351 return NULL;
352 }
353
354 #define DEFAULT_HOME_PREFIX "/home/"
355 #define DEFAULT_SHELL "/dev/null"
356
357 int
decode_reply(grad_request_t * reply,const char * user_name,char * password,struct mu_auth_data ** return_data)358 decode_reply (grad_request_t *reply, const char *user_name, char *password,
359 struct mu_auth_data **return_data)
360 {
361 grad_avp_t *p;
362 int rc;
363
364 uid_t uid = -1;
365 gid_t gid = -1;
366 char *gecos = "RADIUS User";
367 char *dir = NULL;
368 char *shell = NULL;
369 char *mailbox = NULL;
370
371 p = grad_avl_find (reply->avlist, MU_User_Name);
372 if (p)
373 user_name = p->avp_strvalue;
374
375 p = grad_avl_find (reply->avlist, MU_UID);
376 if (p)
377 uid = p->avp_lvalue;
378 else
379 {
380 mu_error (_("radius server did not return UID for `%s'"), user_name);
381 return -1;
382 }
383
384 p = grad_avl_find (reply->avlist, MU_GID);
385 if (p)
386 gid = p->avp_lvalue;
387 else
388 {
389 mu_error (_("radius server did not return GID for `%s'"), user_name);
390 return -1;
391 }
392
393 p = grad_avl_find (reply->avlist, MU_GECOS);
394 if (p)
395 gecos = p->avp_strvalue;
396
397 p = grad_avl_find (reply->avlist, MU_Dir);
398 if (p)
399 dir = strdup (p->avp_strvalue);
400 else /* Try to provide a reasonable default */
401 {
402 dir = malloc (sizeof DEFAULT_HOME_PREFIX + strlen (user_name));
403 if (!dir) /* FIXME: Error code */
404 return 1;
405 strcat (strcpy (dir, DEFAULT_HOME_PREFIX), user_name);
406 }
407
408 p = grad_avl_find (reply->avlist, MU_Shell);
409 if (p)
410 shell = p->avp_strvalue;
411 else
412 shell = DEFAULT_SHELL;
413
414 p = grad_avl_find (reply->avlist, MU_Mailbox);
415 if (p)
416 mailbox = strdup (p->avp_strvalue);
417 else
418 {
419 rc = mu_construct_user_mailbox_url (&mailbox, user_name);
420 if (rc)
421 return rc;
422 }
423
424 rc = mu_auth_data_alloc (return_data,
425 user_name,
426 password,
427 uid,
428 gid,
429 gecos,
430 dir,
431 shell,
432 mailbox,
433 1);
434
435 free (dir);
436 free (mailbox);
437 return rc;
438 }
439
440 int
mu_radius_authenticate(struct mu_auth_data ** return_data MU_ARG_UNUSED,const void * key,void * func_data MU_ARG_UNUSED,void * call_data)441 mu_radius_authenticate (struct mu_auth_data **return_data MU_ARG_UNUSED,
442 const void *key,
443 void *func_data MU_ARG_UNUSED, void *call_data)
444 {
445 int rc;
446 grad_request_t *reply;
447 const struct mu_auth_data *auth_data = key;
448
449 if (!radius_auth_enabled)
450 return ENOSYS;
451
452 if (!auth_request)
453 {
454 mu_error (_("radius request for auth is not specified"));
455 return EINVAL;
456 }
457
458 reply = send_request (auth_request, RT_ACCESS_REQUEST,
459 auth_data->name, (char*) call_data);
460 if (!reply)
461 return EAGAIN;
462
463 switch (reply->code) {
464 case RT_ACCESS_ACCEPT:
465 rc = 0;
466 break;
467
468 case RT_ACCESS_CHALLENGE:
469 /* Should return another code here? */
470 default:
471 rc = MU_ERR_AUTH_FAILURE;
472 }
473
474 grad_request_free (reply);
475
476 return rc;
477 }
478
479 static int
mu_auth_radius_user_by_name(struct mu_auth_data ** return_data,const void * key,void * unused_func_data,void * unused_call_data)480 mu_auth_radius_user_by_name (struct mu_auth_data **return_data,
481 const void *key,
482 void *unused_func_data, void *unused_call_data)
483 {
484 int rc = MU_ERR_AUTH_FAILURE;
485 grad_request_t *reply;
486
487 if (!radius_auth_enabled)
488 return ENOSYS;
489
490 if (!getpwnam_request)
491 {
492 mu_error (_("radius request for getpwnam is not specified"));
493 return MU_ERR_FAILURE;
494 }
495
496 reply = send_request (getpwnam_request, RT_ACCESS_REQUEST, key, NULL);
497 if (!reply)
498 {
499 mu_error (_("radius server did not respond"));
500 rc = EAGAIN;
501 }
502 else
503 {
504 if (reply->code != RT_ACCESS_ACCEPT)
505 mu_error (_("%s: server returned %s"),
506 (char*) key,
507 grad_request_code_to_name (reply->code));
508 else
509 rc = decode_reply (reply, key, "x", return_data);
510
511 grad_request_free (reply);
512 }
513 return rc;
514 }
515
516 static int
mu_auth_radius_user_by_uid(struct mu_auth_data ** return_data,const void * key,void * func_data,void * call_data)517 mu_auth_radius_user_by_uid (struct mu_auth_data **return_data,
518 const void *key,
519 void *func_data, void *call_data)
520 {
521 int rc = MU_ERR_AUTH_FAILURE;
522 grad_request_t *reply;
523 char uidstr[64];
524
525 if (!radius_auth_enabled)
526 return ENOSYS;
527
528 if (!key)
529 return EINVAL;
530
531 if (!getpwuid_request)
532 {
533 mu_error (_("radius request for getpwuid is not specified"));
534 return MU_ERR_FAILURE;
535 }
536
537 snprintf (uidstr, sizeof (uidstr), "%u", *(uid_t*)key);
538 reply = send_request (getpwuid_request, RT_ACCESS_REQUEST, uidstr, NULL);
539 if (!reply)
540 {
541 mu_error (_("radius server did not respond"));
542 rc = EAGAIN;
543 }
544 if (reply->code != RT_ACCESS_ACCEPT)
545 {
546 mu_error (_("uid %s: server returned %s"), uidstr,
547 grad_request_code_to_name (reply->code));
548 }
549 else
550 rc = decode_reply (reply, uidstr, "x", return_data);
551
552 grad_request_free (reply);
553 return rc;
554 }
555
556 struct mu_auth_module mu_auth_radius_module = {
557 .name = "radius",
558 .cfg = mu_radius_param,
559 .commit = module_init,
560 .handler = {
561 [mu_auth_authenticate] = mu_radius_authenticate,
562 [mu_auth_getpwnam] = mu_auth_radius_user_by_name,
563 [mu_auth_getpwuid] = mu_auth_radius_user_by_uid
564 }
565 };
566 #else
567 struct mu_auth_module mu_auth_radius_module = {
568 .name = "radius"
569 };
570 #endif
571
572