1 /*
2 * OpenConnect (SSL + DTLS) VPN client
3 *
4 * Copyright © 2008-2015 Intel Corporation.
5 * Copyright © 2012-2014 Kevin Cernekee <cernekee@gmail.com>
6 *
7 * Author: David Woodhouse <dwmw2@infradead.org>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 */
18
19 #include <config.h>
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <stoken.h>
27
28 #include "openconnect-internal.h"
29
30 #ifndef STOKEN_CHECK_VER
31 #define STOKEN_CHECK_VER(x,y) 0
32 #endif
33
set_libstoken_mode(struct openconnect_info * vpninfo,const char * token_str)34 int set_libstoken_mode(struct openconnect_info *vpninfo, const char *token_str)
35 {
36 int ret;
37 char *file_token = NULL;
38
39 if (!vpninfo->stoken_ctx) {
40 vpninfo->stoken_ctx = stoken_new();
41 if (!vpninfo->stoken_ctx)
42 return -EIO;
43 }
44
45 if (token_str) {
46 switch(token_str[0]) {
47 case '@':
48 token_str++;
49 /* fall through */
50 case '/':
51 ret = openconnect_read_file(vpninfo, token_str, &file_token);
52 if (ret < 0)
53 return ret;
54 }
55 }
56
57 /* Ug. If it's an XML STDID file or a raw token string, we need to
58 * pass its contents to stoken_import_string(). If it's an rcfile,
59 * we need to pass the *filename* to stoken_import_rcfile(). So
60 * let's just depend on stoken_import_string() failing gracefully. */
61 if (file_token) {
62 ret = stoken_import_string(vpninfo->stoken_ctx, file_token);
63 free(file_token);
64 if (ret == -EINVAL)
65 ret = stoken_import_rcfile(vpninfo->stoken_ctx, token_str);
66 } else if (token_str) {
67 ret = stoken_import_string(vpninfo->stoken_ctx, token_str);
68 } else {
69 ret = stoken_import_rcfile(vpninfo->stoken_ctx, NULL);
70 }
71
72 if (ret)
73 return ret;
74
75 vpninfo->token_mode = OC_TOKEN_MODE_STOKEN;
76 return 0;
77 }
78
79
80 /*
81 * A SecurID token can be encrypted with a device ID, a password, both,
82 * or neither. Gather the required information, decrypt the token, and
83 * check the hash to make sure it is sane.
84 *
85 * Return value:
86 * < 0, on error
87 * = 0, on success
88 * = 1, if the user cancelled the form submission
89 * = 2, if the user left the entire form blank and clicked OK
90 */
decrypt_stoken(struct openconnect_info * vpninfo)91 static int decrypt_stoken(struct openconnect_info *vpninfo)
92 {
93 struct oc_auth_form form;
94 struct oc_form_opt opts[2], *opt = opts;
95 char **devid = NULL, **pass = NULL;
96 int ret = 0;
97
98 memset(&form, 0, sizeof(form));
99 memset(&opts, 0, sizeof(opts));
100
101 form.opts = opts;
102 form.message = _("Enter credentials to unlock software token.");
103
104 if (stoken_devid_required(vpninfo->stoken_ctx)) {
105 opt->type = OC_FORM_OPT_TEXT;
106 opt->name = (char *)"devid";
107 opt->label = _("Device ID:");
108 devid = &opt->_value;
109 opt++;
110 }
111 if (stoken_pass_required(vpninfo->stoken_ctx)) {
112 opt->type = OC_FORM_OPT_PASSWORD;
113 opt->name = (char *)"password";
114 opt->label = _("Password:");
115 pass = &opt->_value;
116 opt++;
117 }
118
119 opts[0].next = opts[1].type ? &opts[1] : NULL;
120
121 while (1) {
122 nuke_opt_values(opts);
123
124 if (!opts[0].type) {
125 /* don't bug the user if there's nothing to enter */
126 ret = 0;
127 } else {
128 int some_empty = 0, all_empty = 1;
129
130 /* < 0 for error; 1 if cancelled */
131 ret = process_auth_form(vpninfo, &form);
132 if (ret)
133 break;
134
135 for (opt = opts; opt; opt = opt->next) {
136 if (!opt->_value || !strlen(opt->_value))
137 some_empty = 1;
138 else
139 all_empty = 0;
140 }
141 if (all_empty) {
142 vpn_progress(vpninfo, PRG_INFO,
143 _("User bypassed soft token.\n"));
144 ret = 2;
145 break;
146 }
147 if (some_empty) {
148 vpn_progress(vpninfo, PRG_INFO,
149 _("All fields are required; try again.\n"));
150 continue;
151 }
152 }
153
154 ret = stoken_decrypt_seed(vpninfo->stoken_ctx,
155 pass ? *pass : NULL,
156 devid ? *devid : NULL);
157 if (ret == -EIO || (ret && !devid && !pass)) {
158 vpn_progress(vpninfo, PRG_ERR,
159 _("General failure in libstoken.\n"));
160 break;
161 } else if (ret != 0) {
162 vpn_progress(vpninfo, PRG_INFO,
163 _("Incorrect device ID or password; try again.\n"));
164 continue;
165 }
166
167 vpn_progress(vpninfo, PRG_DEBUG, _("Soft token init was successful.\n"));
168 ret = 0;
169 break;
170 }
171
172 nuke_opt_values(opts);
173 return ret;
174 }
175
get_stoken_details(struct openconnect_info * vpninfo)176 static void get_stoken_details(struct openconnect_info *vpninfo)
177 {
178 #if STOKEN_CHECK_VER(1,3)
179 struct stoken_info *info = stoken_get_info(vpninfo->stoken_ctx);
180
181 if (info) {
182 vpninfo->stoken_concat_pin = !info->uses_pin;
183 vpninfo->stoken_interval = info->interval;
184 return;
185 }
186 #endif
187 vpninfo->stoken_concat_pin = 0;
188 vpninfo->stoken_interval = 60;
189 }
190
191 /*
192 * Return value:
193 * < 0, on error
194 * = 0, on success
195 * = 1, if the user cancelled the form submission
196 */
request_stoken_pin(struct openconnect_info * vpninfo)197 static int request_stoken_pin(struct openconnect_info *vpninfo)
198 {
199 struct oc_auth_form form;
200 struct oc_form_opt opts[1], *opt = opts;
201 int ret = 0;
202
203 if (!vpninfo->stoken_concat_pin && !stoken_pin_required(vpninfo->stoken_ctx))
204 return 0;
205
206 memset(&form, 0, sizeof(form));
207 memset(&opts, 0, sizeof(opts));
208
209 form.opts = opts;
210 form.message = _("Enter software token PIN.");
211
212 opt->type = OC_FORM_OPT_PASSWORD;
213 opt->name = (char *)"password";
214 opt->label = _("PIN:");
215 opt->flags = OC_FORM_OPT_NUMERIC;
216
217 while (1) {
218 char *pin;
219
220 nuke_opt_values(opts);
221
222 /* < 0 for error; 1 if cancelled */
223 ret = process_auth_form(vpninfo, &form);
224 if (ret)
225 break;
226
227 pin = opt->_value;
228 if (!pin || !strlen(pin)) {
229 /* in some cases there really is no PIN */
230 if (vpninfo->stoken_concat_pin)
231 return 0;
232
233 vpn_progress(vpninfo, PRG_INFO,
234 _("All fields are required; try again.\n"));
235 continue;
236 }
237
238 if (!vpninfo->stoken_concat_pin &&
239 stoken_check_pin(vpninfo->stoken_ctx, pin) != 0) {
240 vpn_progress(vpninfo, PRG_INFO,
241 _("Invalid PIN format; try again.\n"));
242 continue;
243 }
244
245 free(vpninfo->stoken_pin);
246 vpninfo->stoken_pin = strdup(pin);
247 if (!vpninfo->stoken_pin)
248 ret = -ENOMEM;
249 break;
250 }
251
252 nuke_opt_values(opts);
253 return ret;
254 }
255
256 /*
257 * If the user clicks OK on the devid/password prompt without entering
258 * any data, we will continue connecting but bypass soft token generation
259 * for the duration of this "obtain_cookie" session. (They might not even
260 * have the credentials that we're prompting for.)
261 *
262 * If the user clicks Cancel, we will abort the connection.
263 *
264 * Return value:
265 * < 0, on error
266 * = 0, on success (or if the user bypassed soft token init)
267 * = 1, if the user cancelled the form submission
268 */
prepare_stoken(struct openconnect_info * vpninfo)269 int prepare_stoken(struct openconnect_info *vpninfo)
270 {
271 int ret;
272
273 vpninfo->token_tries = 0;
274 vpninfo->token_bypassed = 0;
275
276 ret = decrypt_stoken(vpninfo);
277 if (ret == 2) {
278 vpninfo->token_bypassed = 1;
279 return 0;
280 } else if (ret != 0)
281 return ret;
282
283 get_stoken_details(vpninfo);
284 return request_stoken_pin(vpninfo);
285 }
286
287 /* Return value:
288 * < 0, if unable to generate a tokencode
289 * = 0, on success
290 */
can_gen_stoken_code(struct openconnect_info * vpninfo,struct oc_auth_form * form,struct oc_form_opt * opt)291 int can_gen_stoken_code(struct openconnect_info *vpninfo,
292 struct oc_auth_form *form,
293 struct oc_form_opt *opt)
294 {
295 if (vpninfo->token_tries == 0) {
296 vpn_progress(vpninfo, PRG_DEBUG,
297 _("OK to generate INITIAL tokencode\n"));
298 vpninfo->token_time = 0;
299 } else if (vpninfo->token_tries == 1 && form->message &&
300 strcasestr(form->message, "next tokencode")) {
301 vpn_progress(vpninfo, PRG_DEBUG,
302 _("OK to generate NEXT tokencode\n"));
303 vpninfo->token_time += vpninfo->stoken_interval;
304 } else {
305 /* limit the number of retries, to avoid account lockouts */
306 vpn_progress(vpninfo, PRG_INFO,
307 _("Server is rejecting the soft token; switching to manual entry\n"));
308 return -ENOENT;
309 }
310 return 0;
311 }
312
do_gen_stoken_code(struct openconnect_info * vpninfo,struct oc_auth_form * form,struct oc_form_opt * opt)313 int do_gen_stoken_code(struct openconnect_info *vpninfo,
314 struct oc_auth_form *form,
315 struct oc_form_opt *opt)
316 {
317 char tokencode[STOKEN_MAX_TOKENCODE + 1];
318
319 if (!vpninfo->token_time)
320 vpninfo->token_time = time(NULL);
321 vpn_progress(vpninfo, PRG_INFO, _("Generating RSA token code\n"));
322
323 /* This doesn't normally fail */
324 if (stoken_compute_tokencode(vpninfo->stoken_ctx, vpninfo->token_time,
325 vpninfo->stoken_pin, tokencode) < 0) {
326 vpn_progress(vpninfo, PRG_ERR, _("General failure in libstoken.\n"));
327 return -EIO;
328 }
329
330 vpninfo->token_tries++;
331
332 if (asprintf(&opt->_value, "%s%s",
333 (vpninfo->stoken_concat_pin && vpninfo->stoken_pin) ? vpninfo->stoken_pin : "",
334 tokencode) < 0)
335 return -ENOMEM;
336 return 0;
337 }
338
339