1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/otp/otp_state.c - Verify OTP token values using RADIUS */
4 * Copyright 2013 Red Hat, Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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
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.
30 #include "otp_state.h"
39 #define HOST_NAME_MAX 255
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
48 typedef struct token_type_st {
54 krb5_boolean strip_realm;
58 typedef struct token_st {
59 const token_type *type;
64 typedef struct request_st {
80 static void request_send(request *req);
82 static krb5_error_code
83 read_secret_file(const char *secret_file, char **secret)
85 char buf[MAX_SECRET_LEN];
86 krb5_error_code retval;
87 char *filename = NULL;
93 retval = k5_path_join(KDC_DIR, secret_file, &filename);
95 com_err("otp", retval, "Unable to resolve secret file '%s'", filename);
99 file = fopen(filename, "r");
102 com_err("otp", retval, "Unable to open secret file '%s'", filename);
106 if (fgets(buf, sizeof(buf), file) == NULL)
110 com_err("otp", retval, "Unable to read secret file '%s'", filename);
114 /* Strip whitespace. */
115 for (i = 0; buf[i] != '\0'; i++) {
116 if (!isspace(buf[i]))
119 for (j = strlen(buf); j > i; j--) {
120 if (!isspace(buf[j - 1]))
124 *secret = k5memdup0(&buf[i], j - i, &retval);
131 /* Free the contents of a single token type. */
133 token_type_free(token_type *type)
141 profile_free_list(type->indicators);
144 /* Construct the internal default token type. */
145 static krb5_error_code
146 token_type_default(token_type *out)
148 char *name = NULL, *server = NULL, *secret = NULL;
150 memset(out, 0, sizeof(*out));
152 name = strdup(DEFAULT_TYPE_NAME);
155 if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0)
162 out->server = server;
163 out->secret = secret;
164 out->timeout = DEFAULT_TIMEOUT * 1000;
165 out->retries = DEFAULT_RETRIES;
166 out->strip_realm = FALSE;
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)
180 char *server = NULL, *name_copy = NULL, *secret = NULL, *pstr = NULL;
181 char **indicators = NULL;
183 int strip_realm, timeout, retries;
184 krb5_error_code retval;
186 memset(out, 0, sizeof(*out));
189 name_copy = strdup(name);
190 if (name_copy == NULL)
193 /* Set strip_realm. */
194 retval = profile_get_boolean(profile, "otp", name, "strip_realm", TRUE,
199 /* Set the server. */
200 retval = profile_get_string(profile, "otp", name, "server", NULL, &pstr);
204 server = strdup(pstr);
205 profile_release_string(pstr);
206 if (server == NULL) {
210 } else if (asprintf(&server, DEFAULT_SOCKET_FMT, name) < 0) {
215 /* Get the secret (optional for Unix-domain sockets). */
216 retval = profile_get_string(profile, "otp", name, "secret", NULL, &pstr);
220 retval = read_secret_file(pstr, &secret);
221 profile_release_string(pstr);
225 if (server[0] != '/') {
226 com_err("otp", EINVAL, "Secret missing (token type '%s')", name);
231 /* Use the default empty secret for UNIX domain stream sockets. */
233 if (secret == NULL) {
239 /* Get the timeout (profile value in seconds, result in milliseconds). */
240 retval = profile_get_integer(profile, "otp", name, "timeout",
241 DEFAULT_TIMEOUT, &timeout);
246 /* Get the number of retries. */
247 retval = profile_get_integer(profile, "otp", name, "retries",
248 DEFAULT_RETRIES, &retries);
252 /* Get the authentication indicators to assert if this token is used. */
255 keys[2] = "indicator";
257 retval = profile_get_values(profile, keys, &indicators);
258 if (retval == PROF_NO_RELATION)
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;
277 profile_free_list(indicators);
281 /* Free an array of token types. */
283 token_types_free(token_type *types)
290 for (i = 0; types[i].server != NULL; i++)
291 token_type_free(&types[i]);
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)
300 const char *hier[2] = { "otp", NULL };
301 token_type *types = NULL;
303 krb5_error_code retval;
305 krb5_boolean have_default = FALSE;
307 retval = profile_get_subsection_names(profile, hier, &names);
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)
317 /* Leave space for the default (possibly) and the terminator. */
318 types = k5calloc(i + 2, sizeof(token_type), &retval);
322 /* If no default has been specified, use our internal default. */
325 retval = token_type_default(&types[pos++]);
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++]);
341 profile_free_list(names);
342 token_types_free(types);
346 /* Free a null-terminated array of strings. */
348 free_strings(char **list)
352 for (p = list; p != NULL && *p != NULL; p++)
357 /* Free the contents of a single token. */
359 token_free_contents(token *t)
363 free(t->username.data);
364 free_strings(t->indicators);
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)
376 *indicators_out = NULL;
378 if (k5_json_get_tid(val) != K5_JSON_TID_ARRAY)
381 len = k5_json_array_length(arr);
382 indicators = calloc(len + 1, sizeof(*indicators));
383 if (indicators == NULL)
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);
392 indicators[i] = strdup(k5_json_string_utf8(obj));
393 if (indicators[i] == NULL) {
394 free_strings(indicators);
399 *indicators_out = indicators;
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)
408 const char *typename = DEFAULT_TYPE_NAME;
409 const token_type *type = NULL;
410 char *username = NULL, **indicators = NULL;
411 krb5_error_code retval;
416 memset(out, 0, sizeof(*out));
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)
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)
436 flags = type->strip_realm ? KRB5_PRINCIPAL_UNPARSE_NO_REALM : 0;
437 retval = krb5_unparse_name_flags(ctx, princ, flags, &username);
442 /* Get the authentication indicators if specified. */
443 val = k5_json_object_get(obj, "indicators");
445 retval = indicators_decode(ctx, val, &indicators);
453 out->username = string2data(username);
454 out->indicators = indicators;
458 /* Free an array of tokens. */
460 tokens_free(token *tokens)
467 for (i = 0; tokens[i].type != NULL; i++)
468 token_free_contents(&tokens[i]);
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)
478 krb5_error_code retval;
484 /* Decode the config string and make sure it's an array. */
485 retval = k5_json_decode((config != NULL) ? config : "[{}]", &val);
488 if (k5_json_get_tid(val) != K5_JSON_TID_ARRAY) {
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);
498 retval = k5_json_array_add(val, obj);
499 k5_json_release(obj);
508 k5_json_release(val);
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)
517 krb5_error_code retval;
518 k5_json_array arr = NULL;
520 token *tokens = NULL;
523 retval = decode_config_json(config, &arr);
526 len = k5_json_array_length(arr);
528 tokens = k5calloc(len + 1, sizeof(token), &retval);
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) {
538 retval = token_decode(ctx, princ, types, obj, &tokens[i]);
547 k5_json_release(arr);
553 request_free(request *req)
558 krad_attrset_free(req->attrs);
559 tokens_free(req->tokens);
564 otp_state_new(krb5_context ctx, otp_state **out)
566 char hostname[HOST_NAME_MAX + 1];
567 krb5_error_code retval;
572 retval = gethostname(hostname, sizeof(hostname));
576 self = calloc(1, sizeof(otp_state));
580 retval = krb5_get_profile(ctx, &profile);
584 retval = token_types_decode(profile, &self->types);
585 profile_abandon(profile);
589 retval = krad_attrset_new(ctx, &self->attrs);
593 hndata = make_data(hostname, strlen(hostname));
594 retval = krad_attrset_add(self->attrs,
595 krad_attr_name2num("NAS-Identifier"), &hndata);
599 retval = krad_attrset_add_number(self->attrs,
600 krad_attr_name2num("Service-Type"),
601 KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
610 otp_state_free(self);
615 otp_state_free(otp_state *self)
620 krad_attrset_free(self->attrs);
621 krad_client_free(self->radius);
622 token_types_free(self->types);
627 callback(krb5_error_code retval, const krad_packet *rqst,
628 const krad_packet *resp, void *data)
631 token *tok = &req->tokens[req->index];
632 char *const *indicators;
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);
650 /* If we have no more tokens to try, failure! */
651 if (req->tokens[req->index].type == NULL)
654 /* Try the next token. */
659 req->cb(req->data, retval, otp_response_fail, NULL);
664 request_send(request *req)
666 krb5_error_code retval;
667 token *tok = &req->tokens[req->index];
668 const token_type *t = tok->type;
670 retval = krad_attrset_add(req->attrs, krad_attr_name2num("User-Name"),
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,
679 krad_attrset_del(req->attrs, krad_attr_name2num("User-Name"), 0);
686 req->cb(req->data, retval, otp_response_fail, NULL);
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)
695 krb5_error_code retval;
696 request *rqst = NULL;
699 if (state->radius == NULL) {
700 retval = krad_client_new(state->ctx, ctx, &state->radius);
705 rqst = calloc(1, sizeof(request));
707 (*cb)(data, ENOMEM, otp_response_fail, NULL);
714 retval = krad_attrset_copy(state->attrs, &rqst->attrs);
718 retval = krad_attrset_add(rqst->attrs, krad_attr_name2num("User-Password"),
723 retval = tokens_decode(state->ctx, princ, state->types, config,
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);
738 (*cb)(data, retval, otp_response_fail, NULL);