Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/plugins/preauth/otp/otp_state.c
34907 views
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, KRAD_ATTR_NAS_IDENTIFIER, &hndata);
595
if (retval != 0)
596
goto error;
597
598
retval = krad_attrset_add_number(self->attrs, KRAD_ATTR_SERVICE_TYPE,
599
KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
600
if (retval != 0)
601
goto error;
602
603
self->ctx = ctx;
604
*out = self;
605
return 0;
606
607
error:
608
otp_state_free(self);
609
return retval;
610
}
611
612
void
613
otp_state_free(otp_state *self)
614
{
615
if (self == NULL)
616
return;
617
618
krad_attrset_free(self->attrs);
619
krad_client_free(self->radius);
620
token_types_free(self->types);
621
free(self);
622
}
623
624
static void
625
callback(krb5_error_code retval, const krad_packet *rqst,
626
const krad_packet *resp, void *data)
627
{
628
request *req = data;
629
token *tok = &req->tokens[req->index];
630
char *const *indicators;
631
632
req->index++;
633
634
if (retval != 0)
635
goto error;
636
637
/* If we received an accept packet, success! */
638
if (krad_packet_get_code(resp) == KRAD_CODE_ACCESS_ACCEPT) {
639
indicators = tok->indicators;
640
if (indicators == NULL)
641
indicators = tok->type->indicators;
642
req->cb(req->data, retval, otp_response_success, indicators);
643
request_free(req);
644
return;
645
}
646
647
/* If we have no more tokens to try, failure! */
648
if (req->tokens[req->index].type == NULL)
649
goto error;
650
651
/* Try the next token. */
652
request_send(req);
653
return;
654
655
error:
656
req->cb(req->data, retval, otp_response_fail, NULL);
657
request_free(req);
658
}
659
660
static void
661
request_send(request *req)
662
{
663
krb5_error_code retval;
664
token *tok = &req->tokens[req->index];
665
const token_type *t = tok->type;
666
667
retval = krad_attrset_add(req->attrs, KRAD_ATTR_USER_NAME, &tok->username);
668
if (retval != 0)
669
goto error;
670
671
retval = krad_client_send(req->state->radius, KRAD_CODE_ACCESS_REQUEST,
672
req->attrs, t->server, t->secret, t->timeout,
673
t->retries, callback, req);
674
krad_attrset_del(req->attrs, KRAD_ATTR_USER_NAME, 0);
675
if (retval != 0)
676
goto error;
677
678
return;
679
680
error:
681
req->cb(req->data, retval, otp_response_fail, NULL);
682
request_free(req);
683
}
684
685
void
686
otp_state_verify(otp_state *state, verto_ctx *ctx, krb5_const_principal princ,
687
const char *config, const krb5_pa_otp_req *req,
688
otp_cb cb, void *data)
689
{
690
krb5_error_code retval;
691
request *rqst = NULL;
692
char *name;
693
694
if (state->radius == NULL) {
695
retval = krad_client_new(state->ctx, ctx, &state->radius);
696
if (retval != 0)
697
goto error;
698
}
699
700
rqst = calloc(1, sizeof(request));
701
if (rqst == NULL) {
702
(*cb)(data, ENOMEM, otp_response_fail, NULL);
703
return;
704
}
705
rqst->state = state;
706
rqst->data = data;
707
rqst->cb = cb;
708
709
retval = krad_attrset_copy(state->attrs, &rqst->attrs);
710
if (retval != 0)
711
goto error;
712
713
retval = krad_attrset_add(rqst->attrs, KRAD_ATTR_USER_PASSWORD,
714
&req->otp_value);
715
if (retval != 0)
716
goto error;
717
718
retval = tokens_decode(state->ctx, princ, state->types, config,
719
&rqst->tokens);
720
if (retval != 0) {
721
if (krb5_unparse_name(state->ctx, princ, &name) == 0) {
722
com_err("otp", retval,
723
"Can't decode otp config string for principal '%s'", name);
724
krb5_free_unparsed_name(state->ctx, name);
725
}
726
goto error;
727
}
728
729
request_send(rqst);
730
return;
731
732
error:
733
(*cb)(data, retval, otp_response_fail, NULL);
734
request_free(rqst);
735
}
736
737