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