/
auth.c
483 lines (416 loc) · 12.3 KB
1
2
3
/*
* OpenConnect (SSL + DTLS) VPN client
*
4
* Copyright © 2008-2011 Intel Corporation.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
* Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
*
* Author: David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
*
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
26
#include <stdio.h>
27
28
29
30
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
31
#include <string.h>
32
#include <ctype.h>
33
#include <errno.h>
34
35
36
37
#include <libxml/parser.h>
#include <libxml/tree.h>
38
#include "openconnect-internal.h"
39
40
41
42
43
44
45
46
47
48
49
50
static int append_opt(char *body, int bodylen, char *opt, char *name)
{
int len = strlen(body);
if (len) {
if (len >= bodylen - 1)
return -ENOSPC;
body[len++] = '&';
}
while (*opt) {
51
if (isalnum((int)(unsigned char)*opt)) {
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
if (len >= bodylen - 1)
return -ENOSPC;
body[len++] = *opt;
} else {
if (len >= bodylen - 3)
return -ENOSPC;
sprintf(body+len, "%%%02x", *opt);
len += 3;
}
opt++;
}
if (len >= bodylen - 1)
return -ENOSPC;
body[len++] = '=';
68
while (name && *name) {
69
if (isalnum((int)(unsigned char)*name)) {
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
if (len >= bodylen - 1)
return -ENOSPC;
body[len++] = *name;
} else {
if (len >= bodylen - 3)
return -ENOSPC;
sprintf(body+len, "%%%02X", *name);
len += 3;
}
name++;
}
body[len] = 0;
return 0;
}
86
87
88
89
90
91
92
93
94
95
96
97
98
99
static int append_form_opts(struct openconnect_info *vpninfo,
struct oc_auth_form *form, char *body, int bodylen)
{
struct oc_form_opt *opt;
int ret;
for (opt = form->opts; opt; opt = opt->next) {
ret = append_opt(body, bodylen, opt->name, opt->value);
if (ret)
return ret;
}
return 0;
}
100
101
102
103
104
105
106
/*
* Maybe we should offer this choice to the user. So far we've only
* ever seen it offer bogus choices though -- between certificate and
* password authentication, when the former has already failed.
* So we just accept the first option with an auth-type property.
*/
107
static int parse_auth_choice(struct openconnect_info *vpninfo, struct oc_auth_form *form,
108
xmlNode *xml_node)
109
{
110
struct oc_form_opt_select *opt;
111
112
113
114
115
opt = calloc(1, sizeof(*opt));
if (!opt)
return -ENOMEM;
116
opt->form.type = OC_FORM_OPT_SELECT;
117
118
opt->form.name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
opt->form.label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
119
120
if (!opt->form.name) {
121
vpn_progress(vpninfo, PRG_ERR, _("Form choice has no name\n"));
122
free(opt);
123
124
125
126
return -EINVAL;
}
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
127
char *form_id;
128
struct oc_choice *choice;
129
130
131
132
133
134
135
136
137
138
139
if (xml_node->type != XML_ELEMENT_NODE)
continue;
if (strcmp((char *)xml_node->name, "option"))
continue;
form_id = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
if (!form_id)
continue;
140
141
142
143
144
145
146
147
148
149
150
151
152
opt->nr_choices++;
opt = realloc(opt, sizeof(*opt) +
opt->nr_choices * sizeof(*choice));
if (!opt)
return -ENOMEM;
choice = &opt->choices[opt->nr_choices-1];
choice->name = form_id;
choice->label = (char *)xmlNodeGetContent(xml_node);
choice->auth_type = (char *)xmlGetProp(xml_node, (unsigned char *)"auth-type");
choice->override_name = (char *)xmlGetProp(xml_node, (unsigned char *)"override-name");
choice->override_label = (char *)xmlGetProp(xml_node, (unsigned char *)"override-label");
153
}
154
155
156
157
158
/* We link the choice _first_ so it's at the top of what we present
to the user */
opt->form.next = form->opts;
form->opts = &opt->form;
159
160
161
162
163
164
165
166
return 0;
}
/* Return value:
* < 0, on error
* = 0, when form was cancelled
* = 1, when form was parsed
*/
167
static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
168
xmlNode *xml_node, char *body, int bodylen)
169
{
170
char *input_type, *input_name, *input_label;
171
172
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
173
struct oc_form_opt *opt, **p;
174
175
176
177
178
if (xml_node->type != XML_ELEMENT_NODE)
continue;
if (!strcmp((char *)xml_node->name, "select")) {
179
if (parse_auth_choice(vpninfo, form, xml_node))
180
181
182
183
return -EINVAL;
continue;
}
if (strcmp((char *)xml_node->name, "input")) {
184
185
vpn_progress(vpninfo, PRG_TRACE,
_("name %s not input\n"), xml_node->name);
186
187
188
189
190
continue;
}
input_type = (char *)xmlGetProp(xml_node, (unsigned char *)"type");
if (!input_type) {
191
192
vpn_progress(vpninfo, PRG_INFO,
_("No input type in form\n"));
193
194
195
continue;
}
196
197
if (!strcmp(input_type, "submit") || !strcmp(input_type, "reset")) {
free(input_type);
198
continue;
199
}
200
201
202
input_name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
if (!input_name) {
203
204
vpn_progress(vpninfo, PRG_INFO,
_("No input name in form\n"));
205
free(input_type);
206
207
208
209
continue;
}
input_label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
210
opt = calloc(1, sizeof(*opt));
211
212
213
214
if (!opt) {
free(input_type);
free(input_name);
free(input_label);
215
return -ENOMEM;
216
}
217
218
if (!strcmp(input_type, "hidden")) {
219
opt->type = OC_FORM_OPT_HIDDEN;
220
221
opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
} else if (!strcmp(input_type, "text"))
222
opt->type = OC_FORM_OPT_TEXT;
223
else if (!strcmp(input_type, "password"))
224
opt->type = OC_FORM_OPT_PASSWORD;
225
else {
226
vpn_progress(vpninfo, PRG_INFO,
227
228
_("Unknown input type %s in form\n"),
input_type);
229
230
231
free(input_type);
free(input_name);
free(input_label);
232
233
free(opt);
continue;
234
}
235
236
free(input_type);
237
238
239
240
241
242
243
244
opt->name = input_name;
opt->label = input_label;
p = &form->opts;
while (*p)
p = &(*p)->next;
*p = opt;
245
246
}
247
vpn_progress(vpninfo, PRG_TRACE, _("Fixed options give %s\n"), body);
248
249
250
251
return 0;
}
252
static char *xmlnode_msg(xmlNode *xml_node)
253
{
254
char *fmt = (char *)xmlNodeGetContent(xml_node);
255
256
char *result, *params[2], *pct;
int len;
257
int nr_params = 0;
258
259
260
if (!fmt || !fmt[0]) {
free(fmt);
261
return NULL;
262
}
263
264
265
266
267
268
269
270
271
272
273
274
275
276
len = strlen(fmt) + 1;
params[0] = (char *)xmlGetProp(xml_node, (unsigned char *)"param1");
if (params[0])
len += strlen(params[0]);
params[1] = (char *)xmlGetProp(xml_node, (unsigned char *)"param2");
if (params[1])
len += strlen(params[1]);
result = malloc(len);
if (!result) {
result = fmt;
goto out;
277
278
}
279
280
strcpy(result, fmt);
free (fmt);
281
282
283
284
for (pct = strchr(result, '%'); pct;
(pct = strchr(pct, '%'))) {
int paramlen;
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/* We only cope with '%s' */
if (pct[1] != 's')
goto out;
if (params[nr_params]) {
paramlen = strlen(params[nr_params]);
/* Move rest of fmt string up... */
memmove(pct - 1 + paramlen, pct + 2, strlen(pct) - 1);
/* ... and put the string parameter in where the '%s' was */
memcpy(pct, params[nr_params], paramlen);
pct += paramlen;
} else
pct++;
if (++nr_params == 2)
break;
}
out:
free(params[0]);
free(params[1]);
306
return result;
307
308
}
309
310
/* Return value:
* < 0, on error
311
312
313
* = 0, when form parsed and POST required
* = 1, when response was cancelled by user
* = 2, when form indicates that login was already successful
314
315
*/
int parse_xml_response(struct openconnect_info *vpninfo, char *response,
316
317
char *request_body, int req_len, const char **method,
const char **request_body_type)
318
{
319
struct oc_auth_form *form;
320
321
xmlDocPtr xml_doc;
xmlNode *xml_node;
322
int ret;
323
struct vpn_option *opt, *next;
324
325
326
327
328
329
330
form = calloc(1, sizeof(*form));
if (!form)
return -ENOMEM;
xml_doc = xmlReadMemory(response, strlen(response), "noname.xml", NULL, 0);
if (!xml_doc) {
331
332
333
334
vpn_progress(vpninfo, PRG_ERR,
_("Failed to parse server response\n"));
vpn_progress(vpninfo, PRG_TRACE,
_("Response was:%s\n"), response);
335
336
337
free(form);
return -EINVAL;
}
338
339
340
xml_node = xmlDocGetRootElement(xml_doc);
if (xml_node->type != XML_ELEMENT_NODE || strcmp((char *)xml_node->name, "auth")) {
341
342
vpn_progress(vpninfo, PRG_ERR,
_("XML response has no \"auth\" root node\n"));
343
344
345
346
347
348
ret = -EINVAL;
goto out;
}
form->auth_id = (char *)xmlGetProp(xml_node, (unsigned char *)"id");
if (!strcmp(form->auth_id, "success")) {
349
ret = 2;
350
351
352
353
goto out;
}
if (vpninfo->nopasswd) {
354
355
vpn_progress(vpninfo, PRG_ERR,
_("Asked for password but '--no-passwd' set\n"));
356
357
358
359
360
361
362
363
ret = -EPERM;
goto out;
}
for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
if (xml_node->type != XML_ELEMENT_NODE)
continue;
364
365
366
367
368
369
370
371
372
373
if (!strcmp((char *)xml_node->name, "banner")) {
free(form->banner);
form->banner = xmlnode_msg(xml_node);
} else if (!strcmp((char *)xml_node->name, "message")) {
free(form->message);
form->message = xmlnode_msg(xml_node);
} else if (!strcmp((char *)xml_node->name, "error")) {
free(form->error);
form->error = xmlnode_msg(xml_node);
} else if (!strcmp((char *)xml_node->name, "form")) {
374
375
376
377
form->method = (char *)xmlGetProp(xml_node, (unsigned char *)"method");
form->action = (char *)xmlGetProp(xml_node, (unsigned char *)"action");
if (!form->method || !form->action ||
378
strcasecmp(form->method, "POST") || !form->action[0]) {
379
vpn_progress(vpninfo, PRG_ERR,
380
381
_("Cannot handle form method='%s', action='%s'\n"),
form->method, form->action);
382
383
384
ret = -EINVAL;
goto out;
}
385
vpninfo->redirect_url = strdup(form->action);
386
387
388
389
ret = parse_form(vpninfo, form, xml_node, request_body, req_len);
if (ret < 0)
goto out;
390
} else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csd")) {
391
392
393
394
395
396
if (!vpninfo->csd_token)
vpninfo->csd_token = (char *)xmlGetProp(xml_node,
(unsigned char *)"token");
if (!vpninfo->csd_ticket)
vpninfo->csd_ticket = (char *)xmlGetProp(xml_node,
(unsigned char *)"ticket");
397
} else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csdLinux")) {
398
399
400
401
402
403
vpninfo->csd_stuburl = (char *)xmlGetProp(xml_node,
(unsigned char *)"stuburl");
vpninfo->csd_starturl = (char *)xmlGetProp(xml_node,
(unsigned char *)"starturl");
vpninfo->csd_waiturl = (char *)xmlGetProp(xml_node,
(unsigned char *)"waiturl");
404
vpninfo->csd_preurl = strdup(vpninfo->urlpath);
405
406
}
}
407
408
if (vpninfo->csd_token && vpninfo->csd_ticket && vpninfo->csd_starturl && vpninfo->csd_waiturl) {
/* First, redirect to the stuburl -- we'll need to fetch and run that */
409
vpninfo->redirect_url = strdup(vpninfo->csd_stuburl);
410
411
412
413
414
415
416
417
418
419
420
/* AB: remove all cookies */
for (opt = vpninfo->cookies; opt; opt = next) {
next = opt->next;
free(opt->option);
free(opt->value);
free(opt);
}
vpninfo->cookies = NULL;
421
422
423
ret = 0;
goto out;
}
424
if (!form->opts) {
425
if (form->message)
426
vpn_progress(vpninfo, PRG_INFO, "%s\n", form->message);
427
if (form->error)
428
vpn_progress(vpninfo, PRG_ERR, "%s\n", form->error);
429
430
ret = -EPERM;
goto out;
431
432
}
433
if (vpninfo->process_auth_form)
434
ret = vpninfo->process_auth_form(vpninfo->cbdata, form);
435
436
437
438
else {
vpn_progress(vpninfo, PRG_ERR, _("No form handler; cannot authentiate."));
ret = 1;
}
439
440
441
442
if (ret)
goto out;
ret = append_form_opts(vpninfo, form, request_body, req_len);
443
444
445
446
if (!ret) {
*method = "POST";
*request_body_type = "application/x-www-form-urlencoded";
}
447
448
449
out:
xmlFreeDoc(xml_doc);
while (form->opts) {
450
451
struct oc_form_opt *tmp = form->opts->next;
if (form->opts->type == OC_FORM_OPT_TEXT ||
452
453
form->opts->type == OC_FORM_OPT_PASSWORD ||
form->opts->type == OC_FORM_OPT_HIDDEN)
454
free(form->opts->value);
455
456
457
458
459
460
461
462
463
464
465
466
467
468
else if (form->opts->type == OC_FORM_OPT_SELECT) {
struct oc_form_opt_select *sel = (void *)form->opts;
int i;
for (i=0; i < sel->nr_choices; i++) {
free(sel->choices[i].name);
free(sel->choices[i].label);
free(sel->choices[i].auth_type);
free(sel->choices[i].override_name);
free(sel->choices[i].override_label);
}
}
free(form->opts->label);
free(form->opts->name);
469
470
471
free(form->opts);
form->opts = tmp;
}
472
473
474
475
476
477
free(form->error);
free(form->message);
free(form->banner);
free(form->auth_id);
free(form->method);
free(form->action);
478
479
480
free(form);
return ret;
}