stoken: Implement new auth form to gather soft token information
[platform/upstream/openconnect.git] / auth.c
1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2011 Intel Corporation.
5  * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
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  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to:
20  *
21  *   Free Software Foundation, Inc.
22  *   51 Franklin Street, Fifth Floor,
23  *   Boston, MA 02110-1301 USA
24  */
25
26 #include <stdio.h>
27 #include <netdb.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <time.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <errno.h>
34
35 #ifdef LIBSTOKEN_HDR
36 #include LIBSTOKEN_HDR
37 #endif
38
39 #include <libxml/parser.h>
40 #include <libxml/tree.h>
41
42 #include "openconnect-internal.h"
43
44 static int append_opt(char *body, int bodylen, char *opt, char *name)
45 {
46         int len = strlen(body);
47
48         if (len) {
49                 if (len >= bodylen - 1)
50                         return -ENOSPC;
51                 body[len++] = '&';
52         }
53
54         while (*opt) {
55                 if (isalnum((int)(unsigned char)*opt)) {
56                         if (len >= bodylen - 1)
57                                 return -ENOSPC;
58                         body[len++] = *opt;
59                 } else {
60                         if (len >= bodylen - 3)
61                                 return -ENOSPC;
62                         sprintf(body+len, "%%%02x", *opt);
63                         len += 3;
64                 }
65                 opt++;
66         }
67
68         if (len >= bodylen - 1)
69                 return -ENOSPC;
70         body[len++] = '=';
71
72         while (name && *name) {
73                 if (isalnum((int)(unsigned char)*name)) {
74                         if (len >= bodylen - 1)
75                                 return -ENOSPC;
76                         body[len++] = *name;
77                 } else {
78                         if (len >= bodylen - 3)
79                                 return -ENOSPC;
80                         sprintf(body+len, "%%%02X", *name);
81                         len += 3;
82                 }
83                 name++;
84         }
85         body[len] = 0;
86
87         return 0;
88 }
89
90 static int append_form_opts(struct openconnect_info *vpninfo,
91                             struct oc_auth_form *form, char *body, int bodylen)
92 {
93         struct oc_form_opt *opt;
94         int ret;
95
96         for (opt = form->opts; opt; opt = opt->next) {
97                 ret = append_opt(body, bodylen, opt->name, opt->value);
98                 if (ret)
99                         return ret;
100         }
101         return 0;
102 }
103
104 /*
105  * Maybe we should offer this choice to the user. So far we've only
106  * ever seen it offer bogus choices though -- between certificate and
107  * password authentication, when the former has already failed.
108  * So we just accept the first option with an auth-type property.
109  */
110
111 static int parse_auth_choice(struct openconnect_info *vpninfo, struct oc_auth_form *form,
112                              xmlNode *xml_node)
113 {
114         struct oc_form_opt_select *opt;
115
116         opt = calloc(1, sizeof(*opt));
117         if (!opt)
118                 return -ENOMEM;
119
120         opt->form.type = OC_FORM_OPT_SELECT;
121         opt->form.name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
122         opt->form.label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
123
124         if (!opt->form.name) {
125                 vpn_progress(vpninfo, PRG_ERR, _("Form choice has no name\n"));
126                 free(opt);
127                 return -EINVAL;
128         }
129
130         for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
131                 char *form_id;
132                 struct oc_choice *choice;
133
134                 if (xml_node->type != XML_ELEMENT_NODE)
135                         continue;
136
137                 if (strcmp((char *)xml_node->name, "option"))
138                         continue;
139
140                 form_id = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
141                 if (!form_id)
142                         continue;
143
144                 opt->nr_choices++;
145                 opt = realloc(opt, sizeof(*opt) +
146                                    opt->nr_choices * sizeof(*choice));
147                 if (!opt)
148                         return -ENOMEM;
149
150                 choice = &opt->choices[opt->nr_choices-1];
151
152                 choice->name = form_id;
153                 choice->label = (char *)xmlNodeGetContent(xml_node);
154                 choice->auth_type = (char *)xmlGetProp(xml_node, (unsigned char *)"auth-type");
155                 choice->override_name = (char *)xmlGetProp(xml_node, (unsigned char *)"override-name");
156                 choice->override_label = (char *)xmlGetProp(xml_node, (unsigned char *)"override-label");
157         }
158
159         /* We link the choice _first_ so it's at the top of what we present
160            to the user */
161         opt->form.next = form->opts;
162         form->opts = &opt->form;
163         return 0;
164 }
165
166 /* Return value:
167  *  < 0, on error
168  *  = 0, when form was cancelled
169  *  = 1, when form was parsed
170  */
171 static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
172                       xmlNode *xml_node, char *body, int bodylen)
173 {
174         char *input_type, *input_name, *input_label;
175
176         for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
177                 struct oc_form_opt *opt, **p;
178
179                 if (xml_node->type != XML_ELEMENT_NODE)
180                         continue;
181
182                 if (!strcmp((char *)xml_node->name, "select")) {
183                         if (parse_auth_choice(vpninfo, form, xml_node))
184                                 return -EINVAL;
185                         continue;
186                 }
187                 if (strcmp((char *)xml_node->name, "input")) {
188                         vpn_progress(vpninfo, PRG_TRACE,
189                                      _("name %s not input\n"), xml_node->name);
190                         continue;
191                 }
192
193                 input_type = (char *)xmlGetProp(xml_node, (unsigned char *)"type");
194                 if (!input_type) {
195                         vpn_progress(vpninfo, PRG_INFO,
196                                      _("No input type in form\n"));
197                         continue;
198                 }
199
200                 if (!strcmp(input_type, "submit") || !strcmp(input_type, "reset")) {
201                         free(input_type);
202                         continue;
203                 }
204
205                 input_name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
206                 if (!input_name) {
207                         vpn_progress(vpninfo, PRG_INFO,
208                                      _("No input name in form\n"));
209                         free(input_type);
210                         continue;
211                 }
212                 input_label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
213
214                 opt = calloc(1, sizeof(*opt));
215                 if (!opt) {
216                         free(input_type);
217                         free(input_name);
218                         free(input_label);
219                         return -ENOMEM;
220                 }
221
222                 opt->name = input_name;
223                 opt->label = input_label;
224
225                 if (!strcmp(input_type, "hidden")) {
226                         opt->type = OC_FORM_OPT_HIDDEN;
227                         opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
228                 } else if (!strcmp(input_type, "text"))
229                         opt->type = OC_FORM_OPT_TEXT;
230                 else if (!strcmp(input_type, "password"))
231                         opt->type = OC_FORM_OPT_PASSWORD;
232                 else {
233                         vpn_progress(vpninfo, PRG_INFO,
234                                      _("Unknown input type %s in form\n"),
235                                      input_type);
236                         free(input_type);
237                         free(input_name);
238                         free(input_label);
239                         free(opt);
240                         continue;
241                 }
242
243                 free(input_type);
244
245                 p = &form->opts;
246                 while (*p)
247                         p = &(*p)->next;
248
249                 *p = opt;
250         }
251
252         vpn_progress(vpninfo, PRG_TRACE, _("Fixed options give %s\n"), body);
253
254         return 0;
255 }
256
257 static char *xmlnode_msg(xmlNode *xml_node)
258 {
259         char *fmt = (char *)xmlNodeGetContent(xml_node);
260         char *result, *params[2], *pct;
261         int len;
262         int nr_params = 0;
263
264         if (!fmt || !fmt[0]) {
265                 free(fmt);
266                 return NULL;
267         }
268
269         len = strlen(fmt) + 1;
270         
271         params[0] = (char *)xmlGetProp(xml_node, (unsigned char *)"param1");
272         if (params[0])
273                 len += strlen(params[0]);
274         params[1] = (char *)xmlGetProp(xml_node, (unsigned char *)"param2");
275         if (params[1])
276                 len += strlen(params[1]);
277
278         result = malloc(len);
279         if (!result) {
280                 result = fmt;
281                 goto out;
282         }
283
284         strcpy(result, fmt);
285         free (fmt);
286
287         for (pct = strchr(result, '%'); pct;
288              (pct = strchr(pct, '%'))) {
289                 int paramlen;
290
291                 /* We only cope with '%s' */
292                 if (pct[1] != 's')
293                         goto out;
294
295                 if (params[nr_params]) {
296                         paramlen = strlen(params[nr_params]);
297                         /* Move rest of fmt string up... */
298                         memmove(pct - 1 + paramlen, pct + 2, strlen(pct) - 1);
299                         /* ... and put the string parameter in where the '%s' was */
300                         memcpy(pct, params[nr_params], paramlen);
301                         pct += paramlen;
302                 } else
303                         pct++;
304
305                 if (++nr_params == 2)
306                         break;
307         }
308  out:
309         free(params[0]);
310         free(params[1]);
311         return result;
312 }
313
314 /* Return value:
315  *  < 0, on error
316  *  = 0, when form parsed and POST required
317  *  = 1, when response was cancelled by user
318  *  = 2, when form indicates that login was already successful
319  */
320 int parse_xml_response(struct openconnect_info *vpninfo, char *response,
321                        char *request_body, int req_len, const char **method,
322                        const char **request_body_type)
323 {
324         struct oc_auth_form *form;
325         xmlDocPtr xml_doc;
326         xmlNode *xml_node;
327         int ret;
328         struct vpn_option *opt, *next;
329
330         form = calloc(1, sizeof(*form));
331         if (!form)
332                 return -ENOMEM;
333
334         xml_doc = xmlReadMemory(response, strlen(response), "noname.xml", NULL, 0);
335         if (!xml_doc) {
336                 vpn_progress(vpninfo, PRG_ERR,
337                              _("Failed to parse server response\n"));
338                 vpn_progress(vpninfo, PRG_TRACE,
339                              _("Response was:%s\n"), response);
340                 free(form);
341                 return -EINVAL;
342         }
343
344         xml_node = xmlDocGetRootElement(xml_doc);
345         if (xml_node->type != XML_ELEMENT_NODE || strcmp((char *)xml_node->name, "auth")) {
346                 vpn_progress(vpninfo, PRG_ERR,
347                              _("XML response has no \"auth\" root node\n"));
348                 ret = -EINVAL;
349                 goto out;
350         }
351
352         form->auth_id = (char *)xmlGetProp(xml_node, (unsigned char *)"id");
353         if (!strcmp(form->auth_id, "success")) {
354                 ret = 2;
355                 goto out;
356         }
357
358         if (vpninfo->nopasswd) {
359                 vpn_progress(vpninfo, PRG_ERR,
360                              _("Asked for password but '--no-passwd' set\n"));
361                 ret = -EPERM;
362                 goto out;
363         }
364
365         for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
366                 if (xml_node->type != XML_ELEMENT_NODE)
367                         continue;
368
369                 if (!strcmp((char *)xml_node->name, "banner")) {
370                         free(form->banner);
371                         form->banner = xmlnode_msg(xml_node);
372                 } else if (!strcmp((char *)xml_node->name, "message")) {
373                         free(form->message);
374                         form->message = xmlnode_msg(xml_node);
375                 } else if (!strcmp((char *)xml_node->name, "error")) {
376                         free(form->error);
377                         form->error = xmlnode_msg(xml_node);
378                 } else if (!strcmp((char *)xml_node->name, "form")) {
379
380                         form->method = (char *)xmlGetProp(xml_node, (unsigned char *)"method");
381                         form->action = (char *)xmlGetProp(xml_node, (unsigned char *)"action");
382                         if (!form->method || !form->action || 
383                             strcasecmp(form->method, "POST") || !form->action[0]) {
384                                 vpn_progress(vpninfo, PRG_ERR,
385                                              _("Cannot handle form method='%s', action='%s'\n"),
386                                              form->method, form->action);
387                                 ret = -EINVAL;
388                                 goto out;
389                         }
390                         vpninfo->redirect_url = strdup(form->action);
391
392                         ret = parse_form(vpninfo, form, xml_node, request_body, req_len);
393                         if (ret < 0)
394                                 goto out;
395                 } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csd")) {
396                         if (!vpninfo->csd_token)
397                                 vpninfo->csd_token = (char *)xmlGetProp(xml_node,
398                                                                         (unsigned char *)"token");
399                         if (!vpninfo->csd_ticket)
400                                 vpninfo->csd_ticket = (char *)xmlGetProp(xml_node,
401                                                                          (unsigned char *)"ticket");
402                 } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, vpninfo->csd_xmltag)) {
403                         vpninfo->csd_stuburl = (char *)xmlGetProp(xml_node,
404                                                                   (unsigned char *)"stuburl");
405                         vpninfo->csd_starturl = (char *)xmlGetProp(xml_node,
406                                                                    (unsigned char *)"starturl");
407                         vpninfo->csd_waiturl = (char *)xmlGetProp(xml_node,
408                                                                   (unsigned char *)"waiturl");
409                         vpninfo->csd_preurl = strdup(vpninfo->urlpath);
410                 }
411         }
412         if (vpninfo->csd_token && vpninfo->csd_ticket && vpninfo->csd_starturl && vpninfo->csd_waiturl) {
413                 /* First, redirect to the stuburl -- we'll need to fetch and run that */
414                 vpninfo->redirect_url = strdup(vpninfo->csd_stuburl);
415
416                 /* AB: remove all cookies */
417                 for (opt = vpninfo->cookies; opt; opt = next) {
418                         next = opt->next;
419
420                         free(opt->option);
421                         free(opt->value);
422                         free(opt);
423                 }
424                 vpninfo->cookies = NULL;
425
426                 ret = 0;
427                 goto out;
428         }
429         if (!form->opts) {
430                 if (form->message)
431                         vpn_progress(vpninfo, PRG_INFO, "%s\n", form->message);
432                 if (form->error)
433                         vpn_progress(vpninfo, PRG_ERR, "%s\n", form->error);
434                 ret = -EPERM;
435                 goto out;
436         }
437
438         if (vpninfo->process_auth_form)
439                 ret = vpninfo->process_auth_form(vpninfo->cbdata, form);
440         else {
441                 vpn_progress(vpninfo, PRG_ERR, _("No form handler; cannot authenticate.\n"));
442                 ret = 1;
443         }
444         if (ret)
445                 goto out;
446
447         ret = append_form_opts(vpninfo, form, request_body, req_len);
448         if (!ret) {
449                 *method = "POST";
450                 *request_body_type = "application/x-www-form-urlencoded";
451         }
452  out:
453         xmlFreeDoc(xml_doc);
454         while (form->opts) {
455                 struct oc_form_opt *tmp = form->opts->next;
456                 if (form->opts->type == OC_FORM_OPT_TEXT ||
457                     form->opts->type == OC_FORM_OPT_PASSWORD ||
458                     form->opts->type == OC_FORM_OPT_HIDDEN)
459                         free(form->opts->value);
460                 else if (form->opts->type == OC_FORM_OPT_SELECT) {
461                         struct oc_form_opt_select *sel = (void *)form->opts;
462                         int i;
463
464                         for (i=0; i < sel->nr_choices; i++) {
465                                 free(sel->choices[i].name);
466                                 free(sel->choices[i].label);
467                                 free(sel->choices[i].auth_type);
468                                 free(sel->choices[i].override_name);
469                                 free(sel->choices[i].override_label);
470                         }
471                 }
472                 free(form->opts->label);
473                 free(form->opts->name);
474                 free(form->opts);
475                 form->opts = tmp;
476         }
477         free(form->error);
478         free(form->message);
479         free(form->banner);
480         free(form->auth_id);
481         free(form->method);
482         free(form->action);
483         free(form);
484         return ret;
485 }
486
487 static void nuke_opt_values(struct oc_form_opt *opt)
488 {
489         for (; opt; opt = opt->next) {
490                 free(opt->value);
491                 opt->value = NULL;
492         }
493 }
494
495 /*
496  * If the user clicks OK without entering any data, we will continue
497  * connecting but bypass soft token generation for the duration of
498  * this "obtain_cookie" session.
499  *
500  * If the user clicks Cancel, we will abort the connection.
501  *
502  * Return value:
503  *  < 0, on error
504  *  = 0, on success (or if the user bypassed soft token init)
505  *  = 1, if the user cancelled the form submission
506  */
507 int prepare_stoken(struct openconnect_info *vpninfo)
508 {
509 #ifdef LIBSTOKEN_HDR
510         struct oc_auth_form form;
511         struct oc_form_opt opts[3], *opt = opts;
512         char **devid = NULL, **pass = NULL, **pin = NULL;
513         int ret = 0;
514
515         memset(&form, 0, sizeof(form));
516         memset(&opts, 0, sizeof(opts));
517
518         form.opts = opts;
519         form.message = _("Enter credentials to unlock software token.");
520
521         vpninfo->stoken_tries = 0;
522         vpninfo->stoken_bypassed = 0;
523
524         if (stoken_devid_required(vpninfo->stoken_ctx)) {
525                 opt->type = OC_FORM_OPT_TEXT;
526                 opt->name = (char *)"devid";
527                 opt->label = _("Device ID:");
528                 devid = &opt->value;
529                 opt++;
530         }
531         if (stoken_pass_required(vpninfo->stoken_ctx)) {
532                 opt->type = OC_FORM_OPT_PASSWORD;
533                 opt->name = (char *)"password";
534                 opt->label = _("Password:");
535                 pass = &opt->value;
536                 opt++;
537         }
538         if (stoken_pin_required(vpninfo->stoken_ctx)) {
539                 opt->type = OC_FORM_OPT_PASSWORD;
540                 opt->name = (char *)"password";
541                 opt->label = _("PIN:");
542                 pin = &opt->value;
543                 opt++;
544         }
545
546         opts[0].next = opts[1].type ? &opts[1] : NULL;
547         opts[1].next = opts[2].type ? &opts[2] : NULL;
548
549         while (1) {
550                 nuke_opt_values(opts);
551
552                 if (!opts[0].type) {
553                         /* don't bug the user if there's nothing to enter */
554                         ret = 0;
555                 } else if (vpninfo->process_auth_form) {
556                         int some_empty = 0, all_empty = 1;
557
558                         /* < 0 for error; 1 if cancelled */
559                         ret = vpninfo->process_auth_form(vpninfo->cbdata, &form);
560                         if (ret)
561                                 break;
562
563                         for (opt = opts; opt; opt = opt->next) {
564                                 if (!opt->value || !strlen(opt->value))
565                                         some_empty = 1;
566                                 else
567                                         all_empty = 0;
568                         }
569                         if (all_empty) {
570                                 vpn_progress(vpninfo, PRG_INFO,
571                                              _("User bypassed soft token.\n"));
572                                 vpninfo->stoken_bypassed = 1;
573                                 ret = 0;
574                                 break;
575                         }
576                         if (some_empty) {
577                                 vpn_progress(vpninfo, PRG_INFO,
578                                              _("All fields are required; try again.\n"));
579                                 continue;
580                         }
581                 } else {
582                         vpn_progress(vpninfo, PRG_ERR,
583                                      _("No form handler; cannot authenticate.\n"));
584                         ret = -EIO;
585                         break;
586                 }
587
588                 ret = stoken_decrypt_seed(vpninfo->stoken_ctx,
589                                           pass ? *pass : NULL,
590                                           devid ? *devid : NULL);
591                 if (ret == -EIO || (ret && !devid && !pass)) {
592                         vpn_progress(vpninfo, PRG_ERR,
593                                      _("General failure in libstoken.\n"));
594                         break;
595                 } else if (ret != 0) {
596                         vpn_progress(vpninfo, PRG_INFO,
597                                      _("Incorrect device ID or password; try again.\n"));
598                         continue;
599                 }
600
601                 if (pin) {
602                         if (stoken_check_pin(vpninfo->stoken_ctx, *pin) != 0) {
603                                 vpn_progress(vpninfo, PRG_INFO,
604                                              _("Invalid PIN format; try again.\n"));
605                                 continue;
606                         }
607                         free(vpninfo->stoken_pin);
608                         vpninfo->stoken_pin = strdup(*pin);
609                         if (!vpninfo->stoken_pin) {
610                                 ret = -ENOMEM;
611                                 break;
612                         }
613                 }
614                 vpn_progress(vpninfo, PRG_DEBUG, _("Soft token init was successful.\n"));
615                 ret = 0;
616                 break;
617         }
618
619         nuke_opt_values(opts);
620         return ret;
621 #else
622         return -EOPNOTSUPP;
623 #endif
624 }