Add cancellation handling to proxy I/O functions
[platform/upstream/openconnect.git] / auth.c
1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2010 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
34 #include <openssl/err.h>
35 #include <openssl/ui.h>
36
37 #include <libxml/parser.h>
38 #include <libxml/tree.h>
39
40 #include "openconnect-internal.h"
41
42 static int append_opt(char *body, int bodylen, char *opt, char *name)
43 {
44         int len = strlen(body);
45
46         if (len) {
47                 if (len >= bodylen - 1)
48                         return -ENOSPC;
49                 body[len++] = '&';
50         }
51
52         while (*opt) {
53                 if (isalnum((int)(unsigned char)*opt)) {
54                         if (len >= bodylen - 1)
55                                 return -ENOSPC;
56                         body[len++] = *opt;
57                 } else {
58                         if (len >= bodylen - 3)
59                                 return -ENOSPC;
60                         sprintf(body+len, "%%%02x", *opt);
61                         len += 3;
62                 }
63                 opt++;
64         }
65
66         if (len >= bodylen - 1)
67                 return -ENOSPC;
68         body[len++] = '=';
69
70         while (name && *name) {
71                 if (isalnum((int)(unsigned char)*name)) {
72                         if (len >= bodylen - 1)
73                                 return -ENOSPC;
74                         body[len++] = *name;
75                 } else {
76                         if (len >= bodylen - 3)
77                                 return -ENOSPC;
78                         sprintf(body+len, "%%%02X", *name);
79                         len += 3;
80                 }
81                 name++;
82         }
83         body[len] = 0;
84
85         return 0;
86 }
87
88 static int append_form_opts(struct openconnect_info *vpninfo,
89                             struct oc_auth_form *form, char *body, int bodylen)
90 {
91         struct oc_form_opt *opt;
92         int ret;
93
94         for (opt = form->opts; opt; opt = opt->next) {
95                 ret = append_opt(body, bodylen, opt->name, opt->value);
96                 if (ret)
97                         return ret;
98         }
99         return 0;
100 }
101
102 /*
103  * Maybe we should offer this choice to the user. So far we've only
104  * ever seen it offer bogus choices though -- between certificate and
105  * password authentication, when the former has already failed.
106  * So we just accept the first option with an auth-type property.
107  */
108
109 static int parse_auth_choice(struct openconnect_info *vpninfo, struct oc_auth_form *form,
110                              xmlNode *xml_node)
111 {
112         struct oc_form_opt_select *opt;
113
114         opt = calloc(1, sizeof(*opt));
115         if (!opt)
116                 return -ENOMEM;
117
118         opt->form.type = OC_FORM_OPT_SELECT;
119         opt->form.name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
120         opt->form.label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
121
122         if (!opt->form.name) {
123                 vpn_progress(vpninfo, PRG_ERR, _("Form choice has no name\n"));
124                 free(opt);
125                 return -EINVAL;
126         }
127
128         for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
129                 char *form_id;
130                 struct oc_choice *choice;
131
132                 if (xml_node->type != XML_ELEMENT_NODE)
133                         continue;
134
135                 if (strcmp((char *)xml_node->name, "option"))
136                         continue;
137
138                 form_id = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
139                 if (!form_id)
140                         continue;
141
142                 opt->nr_choices++;
143                 opt = realloc(opt, sizeof(*opt) +
144                                    opt->nr_choices * sizeof(*choice));
145                 if (!opt)
146                         return -ENOMEM;
147
148                 choice = &opt->choices[opt->nr_choices-1];
149
150                 choice->name = form_id;
151                 choice->label = (char *)xmlNodeGetContent(xml_node);
152                 choice->auth_type = (char *)xmlGetProp(xml_node, (unsigned char *)"auth-type");
153                 choice->override_name = (char *)xmlGetProp(xml_node, (unsigned char *)"override-name");
154                 choice->override_label = (char *)xmlGetProp(xml_node, (unsigned char *)"override-label");
155         }
156
157         /* We link the choice _first_ so it's at the top of what we present
158            to the user */
159         opt->form.next = form->opts;
160         form->opts = &opt->form;
161         return 0;
162 }
163
164 /* Return value:
165  *  < 0, on error
166  *  = 0, when form was cancelled
167  *  = 1, when form was parsed
168  */
169 static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
170                       xmlNode *xml_node, char *body, int bodylen)
171 {
172         char *input_type, *input_name, *input_label;
173
174         for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
175                 struct oc_form_opt *opt, **p;
176
177                 if (xml_node->type != XML_ELEMENT_NODE)
178                         continue;
179
180                 if (!strcmp((char *)xml_node->name, "select")) {
181                         if (parse_auth_choice(vpninfo, form, xml_node))
182                                 return -EINVAL;
183                         continue;
184                 }
185                 if (strcmp((char *)xml_node->name, "input")) {
186                         vpn_progress(vpninfo, PRG_TRACE,
187                                      _("name %s not input\n"), xml_node->name);
188                         continue;
189                 }
190
191                 input_type = (char *)xmlGetProp(xml_node, (unsigned char *)"type");
192                 if (!input_type) {
193                         vpn_progress(vpninfo, PRG_INFO,
194                                      _("No input type in form\n"));
195                         continue;
196                 }
197
198                 if (!strcmp(input_type, "submit") || !strcmp(input_type, "reset")) {
199                         free(input_type);
200                         continue;
201                 }
202
203                 input_name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
204                 if (!input_name) {
205                         vpn_progress(vpninfo, PRG_INFO,
206                                      _("No input name in form\n"));
207                         free(input_type);
208                         continue;
209                 }
210                 input_label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
211
212                 opt = calloc(1, sizeof(*opt));
213                 if (!opt) {
214                         free(input_type);
215                         free(input_name);
216                         free(input_label);
217                         return -ENOMEM;
218                 }
219
220                 if (!strcmp(input_type, "hidden")) {
221                         opt->type = OC_FORM_OPT_HIDDEN;
222                         opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
223                 } else if (!strcmp(input_type, "text"))
224                         opt->type = OC_FORM_OPT_TEXT;
225                 else if (!strcmp(input_type, "password"))
226                         opt->type = OC_FORM_OPT_PASSWORD;
227                 else {
228                         vpn_progress(vpninfo, PRG_INFO,
229                                      _("Unknown input type %s in form\n"),
230                                      input_type);
231                         free(input_type);
232                         free(input_name);
233                         free(input_label);
234                         free(opt);
235                         continue;
236                 }
237
238                 free(input_type);
239                 opt->name = input_name;
240                 opt->label = input_label;
241
242                 p = &form->opts;
243                 while (*p)
244                         p = &(*p)->next;
245
246                 *p = opt;
247         }
248
249         vpn_progress(vpninfo, PRG_TRACE, _("Fixed options give %s\n"), body);
250
251         return 0;
252 }
253
254 static int process_auth_form(struct openconnect_info *vpninfo,
255                              struct oc_auth_form *form);
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, "csdLinux")) {
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                 ret = process_auth_form(vpninfo, form);
442         if (ret)
443                 goto out;
444
445         ret = append_form_opts(vpninfo, form, request_body, req_len);
446         if (!ret) {
447                 *method = "POST";
448                 *request_body_type = "application/x-www-form-urlencoded";
449         }
450  out:
451         xmlFreeDoc(xml_doc);
452         while (form->opts) {
453                 struct oc_form_opt *tmp = form->opts->next;
454                 if (form->opts->type == OC_FORM_OPT_TEXT ||
455                     form->opts->type == OC_FORM_OPT_PASSWORD ||
456                     form->opts->type == OC_FORM_OPT_HIDDEN)
457                         free(form->opts->value);
458                 else if (form->opts->type == OC_FORM_OPT_SELECT) {
459                         struct oc_form_opt_select *sel = (void *)form->opts;
460                         int i;
461
462                         for (i=0; i < sel->nr_choices; i++) {
463                                 free(sel->choices[i].name);
464                                 free(sel->choices[i].label);
465                                 free(sel->choices[i].auth_type);
466                                 free(sel->choices[i].override_name);
467                                 free(sel->choices[i].override_label);
468                         }
469                 }
470                 free(form->opts->label);
471                 free(form->opts->name);
472                 free(form->opts);
473                 form->opts = tmp;
474         }
475         free(form->error);
476         free(form->message);
477         free(form->banner);
478         free(form->auth_id);
479         free(form->method);
480         free(form->action);
481         free(form);
482         return ret;
483 }
484
485
486
487 /* Return value:
488  *  < 0, on error
489  *  = 0, when form was parsed and POST required
490  *  = 1, when response was cancelled by user
491  */
492 static int process_auth_form(struct openconnect_info *vpninfo,
493                              struct oc_auth_form *form)
494 {
495         UI *ui = UI_new();
496         char banner_buf[1024], msg_buf[1024], err_buf[1024];
497         char choice_prompt[1024], choice_resp[80];
498         int ret = 0, input_count=0;
499         struct oc_form_opt *opt;
500         struct oc_form_opt_select *select_opt = NULL;
501
502         choice_resp[0] = 0;
503
504         if (!ui) {
505                 vpn_progress(vpninfo, PRG_ERR, _("Failed to create UI\n"));
506                 return -EINVAL;
507         }
508         if (form->banner) {
509                 banner_buf[1023] = 0;
510                 snprintf(banner_buf, 1023, "%s\n", form->banner);
511                 UI_add_info_string(ui, banner_buf);
512         }
513         if (form->error) {
514                 err_buf[1023] = 0;
515                 snprintf(err_buf, 1023, "%s\n", form->error);
516                 UI_add_error_string(ui, err_buf);
517         }
518         if (form->message) {
519                 msg_buf[1023] = 0;
520                 snprintf(msg_buf, 1023, "%s\n", form->message);
521                 UI_add_info_string(ui, msg_buf);
522         }
523
524         /* scan for select options first so they are displayed first */
525         for (opt = form->opts; opt; opt = opt->next) {
526                 if (opt->type == OC_FORM_OPT_SELECT) {
527                         struct oc_choice *choice = NULL;
528                         int i;
529
530                         select_opt = (void *)opt;
531
532                         if (!select_opt->nr_choices)
533                                 continue;
534
535                         if (vpninfo->authgroup &&
536                             !strcmp(opt->name, "group_list")) {
537                                 for (i = 0; i < select_opt->nr_choices; i++) {
538                                         choice = &select_opt->choices[i];
539
540                                         if (!strcmp(vpninfo->authgroup,
541                                                     choice->label)) {
542                                                 opt->value = choice->name;
543                                                 break;
544                                         }
545                                 }
546                                 if (!opt->value)
547                                         vpn_progress(vpninfo, PRG_ERR,
548                                                      _("Auth choice \"%s\" not available\n"),
549                                                      vpninfo->authgroup);
550                         }
551                         if (!opt->value && select_opt->nr_choices == 1) {
552                                 choice = &select_opt->choices[0];
553                                 opt->value = choice->name;
554                         }
555                         if (opt->value) {
556                                 select_opt = NULL;
557                                 continue;
558                         }
559                         snprintf(choice_prompt, 1023, "%s [", opt->label);
560                         for (i = 0; i < select_opt->nr_choices; i++) {
561                                 choice = &select_opt->choices[i];
562                                 if (i)
563                                         strncat(choice_prompt, "|", 1023 - strlen(choice_prompt));
564
565                                 strncat(choice_prompt, choice->label, 1023 - strlen(choice_prompt));
566                         }
567                         strncat(choice_prompt, "]:", 1023 - strlen(choice_prompt));
568
569                         UI_add_input_string(ui, choice_prompt, UI_INPUT_FLAG_ECHO, choice_resp, 1, 80);
570                         input_count++;
571                 }
572         }
573
574         for (opt = form->opts; opt; opt = opt->next) {
575
576                 if (opt->type == OC_FORM_OPT_TEXT) {
577                         if (vpninfo->username &&
578                             !strcmp(opt->name, "username")) {
579                                 opt->value = strdup(vpninfo->username);
580                                 if (!opt->value) {
581                                         ret = -ENOMEM;
582                                         goto out_ui;
583                                 }
584                         } else {
585                                 opt->value=malloc(80);
586                                 if (!opt->value) {
587                                         ret = -ENOMEM;
588                                         goto out_ui;
589                                 }
590                                 UI_add_input_string(ui, opt->label, UI_INPUT_FLAG_ECHO, opt->value, 1, 80);
591                                 input_count++;
592                         }
593
594                 } else if (opt->type == OC_FORM_OPT_PASSWORD) {
595                         if (vpninfo->password &&
596                             !strcmp(opt->name, "password")) {
597                                 opt->value = strdup(vpninfo->password);
598                                 vpninfo->password = NULL;
599                                 if (!opt->value) {
600                                         ret = -ENOMEM;
601                                         goto out_ui;
602                                 }
603                         } else {
604                                 opt->value=malloc(80);
605                                 if (!opt->value) {
606                                         ret = -ENOMEM;
607                                         goto out_ui;
608                                 }
609                                 UI_add_input_string(ui, opt->label, 0, opt->value, 1, 80);
610                                 input_count++;
611                         }
612
613                 }
614         }
615
616         if (!input_count) {
617                 ret = 0;
618                 goto out_ui;
619         }
620
621         switch (UI_process(ui)) {
622         case -2:
623                 /* cancelled */
624                 ret = 1;
625                 goto out_ui;
626         case -1:
627                 /* error */
628                 vpn_progress(vpninfo, PRG_ERR, _("Invalid inputs\n"));
629                 ret = -EINVAL;
630         out_ui:
631                 UI_free(ui);
632                 return ret;
633         }
634
635         UI_free(ui);
636
637         if (select_opt) {
638                 struct oc_choice *choice = NULL;
639                 int i;
640
641                 for (i = 0; i < select_opt->nr_choices; i++) {
642                         choice = &select_opt->choices[i];
643
644                         if (!strcmp(choice_resp, choice->label)) {
645                                 select_opt->form.value = choice->name;
646                                 break;
647                         }
648                 }
649                 if (!select_opt->form.value) {
650                         vpn_progress(vpninfo, PRG_ERR,
651                                      _("Auth choice \"%s\" not valid\n"),
652                                      choice_resp);
653                         return -EINVAL;
654                 }
655         }
656
657         if (vpninfo->password) {
658                 free(vpninfo->password);
659                 vpninfo->password = NULL;
660         }
661
662         return 0;
663 }