Imported Upstream version 1.20.1
[platform/upstream/krb5.git] / src / plugins / preauth / otp / otp_state.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/otp/otp_state.c - Verify OTP token values using RADIUS */
3 /*
4  * Copyright 2013 Red Hat, Inc.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *    1. Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *
12  *    2. Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in
14  *       the documentation and/or other materials provided with the
15  *       distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "otp_state.h"
31
32 #include <krad.h>
33 #include <k5-json.h>
34
35 #include <ctype.h>
36
37 #ifndef HOST_NAME_MAX
38 /* SUSv2 */
39 #define HOST_NAME_MAX 255
40 #endif
41
42 #define DEFAULT_TYPE_NAME "DEFAULT"
43 #define DEFAULT_SOCKET_FMT KDC_RUN_DIR "/%s.socket"
44 #define DEFAULT_TIMEOUT 5
45 #define DEFAULT_RETRIES 3
46 #define MAX_SECRET_LEN 1024
47
48 typedef struct token_type_st {
49     char *name;
50     char *server;
51     char *secret;
52     int timeout;
53     size_t retries;
54     krb5_boolean strip_realm;
55     char **indicators;
56 } token_type;
57
58 typedef struct token_st {
59     const token_type *type;
60     krb5_data username;
61     char **indicators;
62 } token;
63
64 typedef struct request_st {
65     otp_state *state;
66     token *tokens;
67     ssize_t index;
68     otp_cb cb;
69     void *data;
70     krad_attrset *attrs;
71 } request;
72
73 struct otp_state_st {
74     krb5_context ctx;
75     token_type *types;
76     krad_client *radius;
77     krad_attrset *attrs;
78 };
79
80 static void request_send(request *req);
81
82 static krb5_error_code
83 read_secret_file(const char *secret_file, char **secret)
84 {
85     char buf[MAX_SECRET_LEN];
86     krb5_error_code retval;
87     char *filename = NULL;
88     FILE *file;
89     size_t i, j;
90
91     *secret = NULL;
92
93     retval = k5_path_join(KDC_DIR, secret_file, &filename);
94     if (retval != 0) {
95         com_err("otp", retval, "Unable to resolve secret file '%s'", filename);
96         goto cleanup;
97     }
98
99     file = fopen(filename, "r");
100     if (file == NULL) {
101         retval = errno;
102         com_err("otp", retval, "Unable to open secret file '%s'", filename);
103         goto cleanup;
104     }
105
106     if (fgets(buf, sizeof(buf), file) == NULL)
107         retval = EIO;
108     fclose(file);
109     if (retval != 0) {
110         com_err("otp", retval, "Unable to read secret file '%s'", filename);
111         goto cleanup;
112     }
113
114     /* Strip whitespace. */
115     for (i = 0; buf[i] != '\0'; i++) {
116         if (!isspace(buf[i]))
117             break;
118     }
119     for (j = strlen(buf); j > i; j--) {
120         if (!isspace(buf[j - 1]))
121             break;
122     }
123
124     *secret = k5memdup0(&buf[i], j - i, &retval);
125
126 cleanup:
127     free(filename);
128     return retval;
129 }
130
131 /* Free the contents of a single token type. */
132 static void
133 token_type_free(token_type *type)
134 {
135     if (type == NULL)
136         return;
137
138     free(type->name);
139     free(type->server);
140     free(type->secret);
141     profile_free_list(type->indicators);
142 }
143
144 /* Construct the internal default token type. */
145 static krb5_error_code
146 token_type_default(token_type *out)
147 {
148     char *name = NULL, *server = NULL, *secret = NULL;
149
150     memset(out, 0, sizeof(*out));
151
152     name = strdup(DEFAULT_TYPE_NAME);
153     if (name == NULL)
154         goto oom;
155     if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0)
156         goto oom;
157     secret = strdup("");
158     if (secret == NULL)
159         goto oom;
160
161     out->name = name;
162     out->server = server;
163     out->secret = secret;
164     out->timeout = DEFAULT_TIMEOUT * 1000;
165     out->retries = DEFAULT_RETRIES;
166     out->strip_realm = FALSE;
167     return 0;
168
169 oom:
170     free(name);
171     free(server);
172     free(secret);
173     return ENOMEM;
174 }
175
176 /* Decode a single token type from the profile. */
177 static krb5_error_code
178 token_type_decode(profile_t profile, const char *name, token_type *out)
179 {
180     char *server = NULL, *name_copy = NULL, *secret = NULL, *pstr = NULL;
181     char **indicators = NULL;
182     const char *keys[4];
183     int strip_realm, timeout, retries;
184     krb5_error_code retval;
185
186     memset(out, 0, sizeof(*out));
187
188     /* Set the name. */
189     name_copy = strdup(name);
190     if (name_copy == NULL)
191         return ENOMEM;
192
193     /* Set strip_realm. */
194     retval = profile_get_boolean(profile, "otp", name, "strip_realm", TRUE,
195                                  &strip_realm);
196     if (retval != 0)
197         goto cleanup;
198
199     /* Set the server. */
200     retval = profile_get_string(profile, "otp", name, "server", NULL, &pstr);
201     if (retval != 0)
202         goto cleanup;
203     if (pstr != NULL) {
204         server = strdup(pstr);
205         profile_release_string(pstr);
206         if (server == NULL) {
207             retval = ENOMEM;
208             goto cleanup;
209         }
210     } else if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0) {
211         retval = ENOMEM;
212         goto cleanup;
213     }
214
215     /* Get the secret (optional for Unix-domain sockets). */
216     retval = profile_get_string(profile, "otp", name, "secret", NULL, &pstr);
217     if (retval != 0)
218         goto cleanup;
219     if (pstr != NULL) {
220         retval = read_secret_file(pstr, &secret);
221         profile_release_string(pstr);
222         if (retval != 0)
223             goto cleanup;
224     } else {
225         if (server[0] != '/') {
226             com_err("otp", EINVAL, "Secret missing (token type '%s')", name);
227             retval = EINVAL;
228             goto cleanup;
229         }
230
231         /* Use the default empty secret for UNIX domain stream sockets. */
232         secret = strdup("");
233         if (secret == NULL) {
234             retval = ENOMEM;
235             goto cleanup;
236         }
237     }
238
239     /* Get the timeout (profile value in seconds, result in milliseconds). */
240     retval = profile_get_integer(profile, "otp", name, "timeout",
241                                  DEFAULT_TIMEOUT, &timeout);
242     if (retval != 0)
243         goto cleanup;
244     timeout *= 1000;
245
246     /* Get the number of retries. */
247     retval = profile_get_integer(profile, "otp", name, "retries",
248                                  DEFAULT_RETRIES, &retries);
249     if (retval != 0)
250         goto cleanup;
251
252     /* Get the authentication indicators to assert if this token is used. */
253     keys[0] = "otp";
254     keys[1] = name;
255     keys[2] = "indicator";
256     keys[3] = NULL;
257     retval = profile_get_values(profile, keys, &indicators);
258     if (retval == PROF_NO_RELATION)
259         retval = 0;
260     if (retval != 0)
261         goto cleanup;
262
263     out->name = name_copy;
264     out->server = server;
265     out->secret = secret;
266     out->timeout = timeout;
267     out->retries = retries;
268     out->strip_realm = strip_realm;
269     out->indicators = indicators;
270     name_copy = server = secret = NULL;
271     indicators = NULL;
272
273 cleanup:
274     free(name_copy);
275     free(server);
276     free(secret);
277     profile_free_list(indicators);
278     return retval;
279 }
280
281 /* Free an array of token types. */
282 static void
283 token_types_free(token_type *types)
284 {
285     size_t i;
286
287     if (types == NULL)
288         return;
289
290     for (i = 0; types[i].server != NULL; i++)
291         token_type_free(&types[i]);
292
293     free(types);
294 }
295
296 /* Decode an array of token types from the profile. */
297 static krb5_error_code
298 token_types_decode(profile_t profile, token_type **out)
299 {
300     const char *hier[2] = { "otp", NULL };
301     token_type *types = NULL;
302     char **names = NULL;
303     krb5_error_code retval;
304     size_t i, pos;
305     krb5_boolean have_default = FALSE;
306
307     retval = profile_get_subsection_names(profile, hier, &names);
308     if (retval != 0)
309         return retval;
310
311     /* Check if any of the profile subsections overrides the default. */
312     for (i = 0; names[i] != NULL; i++) {
313         if (strcmp(names[i], DEFAULT_TYPE_NAME) == 0)
314             have_default = TRUE;
315     }
316
317     /* Leave space for the default (possibly) and the terminator. */
318     types = k5calloc(i + 2, sizeof(token_type), &retval);
319     if (types == NULL)
320         goto cleanup;
321
322     /* If no default has been specified, use our internal default. */
323     pos = 0;
324     if (!have_default) {
325         retval = token_type_default(&types[pos++]);
326         if (retval != 0)
327             goto cleanup;
328     }
329
330     /* Decode each profile section into a token type element. */
331     for (i = 0; names[i] != NULL; i++) {
332         retval = token_type_decode(profile, names[i], &types[pos++]);
333         if (retval != 0)
334             goto cleanup;
335     }
336
337     *out = types;
338     types = NULL;
339
340 cleanup:
341     profile_free_list(names);
342     token_types_free(types);
343     return retval;
344 }
345
346 /* Free a null-terminated array of strings. */
347 static void
348 free_strings(char **list)
349 {
350     char **p;
351
352     for (p = list; p != NULL && *p != NULL; p++)
353         free(*p);
354     free(list);
355 }
356
357 /* Free the contents of a single token. */
358 static void
359 token_free_contents(token *t)
360 {
361     if (t == NULL)
362         return;
363     free(t->username.data);
364     free_strings(t->indicators);
365 }
366
367 /* Decode a JSON array of strings into a null-terminated list of C strings. */
368 static krb5_error_code
369 indicators_decode(krb5_context ctx, k5_json_value val, char ***indicators_out)
370 {
371     k5_json_array arr;
372     k5_json_value obj;
373     char **indicators;
374     size_t len, i;
375
376     *indicators_out = NULL;
377
378     if (k5_json_get_tid(val) != K5_JSON_TID_ARRAY)
379         return EINVAL;
380     arr = val;
381     len = k5_json_array_length(arr);
382     indicators = calloc(len + 1, sizeof(*indicators));
383     if (indicators == NULL)
384         return ENOMEM;
385
386     for (i = 0; i < len; i++) {
387         obj = k5_json_array_get(arr, i);
388         if (k5_json_get_tid(obj) != K5_JSON_TID_STRING) {
389             free_strings(indicators);
390             return EINVAL;
391         }
392         indicators[i] = strdup(k5_json_string_utf8(obj));
393         if (indicators[i] == NULL) {
394             free_strings(indicators);
395             return ENOMEM;
396         }
397     }
398
399     *indicators_out = indicators;
400     return 0;
401 }
402
403 /* Decode a single token from a JSON token object. */
404 static krb5_error_code
405 token_decode(krb5_context ctx, krb5_const_principal princ,
406              const token_type *types, k5_json_object obj, token *out)
407 {
408     const char *typename = DEFAULT_TYPE_NAME;
409     const token_type *type = NULL;
410     char *username = NULL, **indicators = NULL;
411     krb5_error_code retval;
412     k5_json_value val;
413     size_t i;
414     int flags;
415
416     memset(out, 0, sizeof(*out));
417
418     /* Find the token type. */
419     val = k5_json_object_get(obj, "type");
420     if (val != NULL && k5_json_get_tid(val) == K5_JSON_TID_STRING)
421         typename = k5_json_string_utf8(val);
422     for (i = 0; types[i].server != NULL; i++) {
423         if (strcmp(typename, types[i].name) == 0)
424             type = &types[i];
425     }
426     if (type == NULL)
427         return EINVAL;
428
429     /* Get the username, either from obj or from unparsing the principal. */
430     val = k5_json_object_get(obj, "username");
431     if (val != NULL && k5_json_get_tid(val) == K5_JSON_TID_STRING) {
432         username = strdup(k5_json_string_utf8(val));
433         if (username == NULL)
434             return ENOMEM;
435     } else {
436         flags = type->strip_realm ? KRB5_PRINCIPAL_UNPARSE_NO_REALM : 0;
437         retval = krb5_unparse_name_flags(ctx, princ, flags, &username);
438         if (retval != 0)
439             return retval;
440     }
441
442     /* Get the authentication indicators if specified. */
443     val = k5_json_object_get(obj, "indicators");
444     if (val != NULL) {
445         retval = indicators_decode(ctx, val, &indicators);
446         if (retval != 0) {
447             free(username);
448             return retval;
449         }
450     }
451
452     out->type = type;
453     out->username = string2data(username);
454     out->indicators = indicators;
455     return 0;
456 }
457
458 /* Free an array of tokens. */
459 static void
460 tokens_free(token *tokens)
461 {
462     size_t i;
463
464     if (tokens == NULL)
465         return;
466
467     for (i = 0; tokens[i].type != NULL; i++)
468         token_free_contents(&tokens[i]);
469
470     free(tokens);
471 }
472
473 /* Decode a principal config string into a JSON array.  Treat an empty string
474  * or array as if it were "[{}]" which uses the default token type. */
475 static krb5_error_code
476 decode_config_json(const char *config, k5_json_array *out)
477 {
478     krb5_error_code retval;
479     k5_json_value val;
480     k5_json_object obj;
481
482     *out = NULL;
483
484     /* Decode the config string and make sure it's an array. */
485     retval = k5_json_decode((config != NULL) ? config : "[{}]", &val);
486     if (retval != 0)
487         goto error;
488     if (k5_json_get_tid(val) != K5_JSON_TID_ARRAY) {
489         retval = EINVAL;
490         goto error;
491     }
492
493     /* If the array is empty, add in an empty object. */
494     if (k5_json_array_length(val) == 0) {
495         retval = k5_json_object_create(&obj);
496         if (retval != 0)
497             goto error;
498         retval = k5_json_array_add(val, obj);
499         k5_json_release(obj);
500         if (retval != 0)
501             goto error;
502     }
503
504     *out = val;
505     return 0;
506
507 error:
508     k5_json_release(val);
509     return retval;
510 }
511
512 /* Decode an array of tokens from the configuration string. */
513 static krb5_error_code
514 tokens_decode(krb5_context ctx, krb5_const_principal princ,
515               const token_type *types, const char *config, token **out)
516 {
517     krb5_error_code retval;
518     k5_json_array arr = NULL;
519     k5_json_value obj;
520     token *tokens = NULL;
521     size_t len, i;
522
523     retval = decode_config_json(config, &arr);
524     if (retval != 0)
525         return retval;
526     len = k5_json_array_length(arr);
527
528     tokens = k5calloc(len + 1, sizeof(token), &retval);
529     if (tokens == NULL)
530         goto cleanup;
531
532     for (i = 0; i < len; i++) {
533         obj = k5_json_array_get(arr, i);
534         if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT) {
535             retval = EINVAL;
536             goto cleanup;
537         }
538         retval = token_decode(ctx, princ, types, obj, &tokens[i]);
539         if (retval != 0)
540             goto cleanup;
541     }
542
543     *out = tokens;
544     tokens = NULL;
545
546 cleanup:
547     k5_json_release(arr);
548     tokens_free(tokens);
549     return retval;
550 }
551
552 static void
553 request_free(request *req)
554 {
555     if (req == NULL)
556         return;
557
558     krad_attrset_free(req->attrs);
559     tokens_free(req->tokens);
560     free(req);
561 }
562
563 krb5_error_code
564 otp_state_new(krb5_context ctx, otp_state **out)
565 {
566     char hostname[HOST_NAME_MAX + 1];
567     krb5_error_code retval;
568     profile_t profile;
569     krb5_data hndata;
570     otp_state *self;
571
572     retval = gethostname(hostname, sizeof(hostname));
573     if (retval != 0)
574         return retval;
575
576     self = calloc(1, sizeof(otp_state));
577     if (self == NULL)
578         return ENOMEM;
579
580     retval = krb5_get_profile(ctx, &profile);
581     if (retval != 0)
582         goto error;
583
584     retval = token_types_decode(profile, &self->types);
585     profile_abandon(profile);
586     if (retval != 0)
587         goto error;
588
589     retval = krad_attrset_new(ctx, &self->attrs);
590     if (retval != 0)
591         goto error;
592
593     hndata = make_data(hostname, strlen(hostname));
594     retval = krad_attrset_add(self->attrs,
595                               krad_attr_name2num("NAS-Identifier"), &hndata);
596     if (retval != 0)
597         goto error;
598
599     retval = krad_attrset_add_number(self->attrs,
600                                      krad_attr_name2num("Service-Type"),
601                                      KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
602     if (retval != 0)
603         goto error;
604
605     self->ctx = ctx;
606     *out = self;
607     return 0;
608
609 error:
610     otp_state_free(self);
611     return retval;
612 }
613
614 void
615 otp_state_free(otp_state *self)
616 {
617     if (self == NULL)
618         return;
619
620     krad_attrset_free(self->attrs);
621     krad_client_free(self->radius);
622     token_types_free(self->types);
623     free(self);
624 }
625
626 static void
627 callback(krb5_error_code retval, const krad_packet *rqst,
628          const krad_packet *resp, void *data)
629 {
630     request *req = data;
631     token *tok = &req->tokens[req->index];
632     char *const *indicators;
633
634     req->index++;
635
636     if (retval != 0)
637         goto error;
638
639     /* If we received an accept packet, success! */
640     if (krad_packet_get_code(resp) ==
641         krad_code_name2num("Access-Accept")) {
642         indicators = tok->indicators;
643         if (indicators == NULL)
644             indicators = tok->type->indicators;
645         req->cb(req->data, retval, otp_response_success, indicators);
646         request_free(req);
647         return;
648     }
649
650     /* If we have no more tokens to try, failure! */
651     if (req->tokens[req->index].type == NULL)
652         goto error;
653
654     /* Try the next token. */
655     request_send(req);
656     return;
657
658 error:
659     req->cb(req->data, retval, otp_response_fail, NULL);
660     request_free(req);
661 }
662
663 static void
664 request_send(request *req)
665 {
666     krb5_error_code retval;
667     token *tok = &req->tokens[req->index];
668     const token_type *t = tok->type;
669
670     retval = krad_attrset_add(req->attrs, krad_attr_name2num("User-Name"),
671                               &tok->username);
672     if (retval != 0)
673         goto error;
674
675     retval = krad_client_send(req->state->radius,
676                               krad_code_name2num("Access-Request"), req->attrs,
677                               t->server, t->secret, t->timeout, t->retries,
678                               callback, req);
679     krad_attrset_del(req->attrs, krad_attr_name2num("User-Name"), 0);
680     if (retval != 0)
681         goto error;
682
683     return;
684
685 error:
686     req->cb(req->data, retval, otp_response_fail, NULL);
687     request_free(req);
688 }
689
690 void
691 otp_state_verify(otp_state *state, verto_ctx *ctx, krb5_const_principal princ,
692                  const char *config, const krb5_pa_otp_req *req,
693                  otp_cb cb, void *data)
694 {
695     krb5_error_code retval;
696     request *rqst = NULL;
697     char *name;
698
699     if (state->radius == NULL) {
700         retval = krad_client_new(state->ctx, ctx, &state->radius);
701         if (retval != 0)
702             goto error;
703     }
704
705     rqst = calloc(1, sizeof(request));
706     if (rqst == NULL) {
707         (*cb)(data, ENOMEM, otp_response_fail, NULL);
708         return;
709     }
710     rqst->state = state;
711     rqst->data = data;
712     rqst->cb = cb;
713
714     retval = krad_attrset_copy(state->attrs, &rqst->attrs);
715     if (retval != 0)
716         goto error;
717
718     retval = krad_attrset_add(rqst->attrs, krad_attr_name2num("User-Password"),
719                               &req->otp_value);
720     if (retval != 0)
721         goto error;
722
723     retval = tokens_decode(state->ctx, princ, state->types, config,
724                            &rqst->tokens);
725     if (retval != 0) {
726         if (krb5_unparse_name(state->ctx, princ, &name) == 0) {
727             com_err("otp", retval,
728                     "Can't decode otp config string for principal '%s'", name);
729             krb5_free_unparsed_name(state->ctx, name);
730         }
731         goto error;
732     }
733
734     request_send(rqst);
735     return;
736
737 error:
738     (*cb)(data, retval, otp_response_fail, NULL);
739     request_free(rqst);
740 }