1 /*
2 * OpenConnect (SSL + DTLS) VPN client
3 *
4 * Copyright © 2008-2015 Intel Corporation.
5 *
6 * Author: David Woodhouse <dwmw2@infradead.org>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 */
17
18 #include <config.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <string.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #ifndef _WIN32
26 #include <sys/wait.h>
27 #endif
28 #include <errno.h>
29 #include <ctype.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include "openconnect-internal.h"
34
script_setenv(struct openconnect_info * vpninfo,const char * opt,const char * val,int trunc,int append)35 int script_setenv(struct openconnect_info *vpninfo,
36 const char *opt, const char *val, int trunc, int append)
37 {
38 struct oc_vpn_option *p;
39 char *str;
40
41 for (p = vpninfo->script_env; p; p = p->next) {
42 if (!strcmp(opt, p->option)) {
43 if (append) {
44 if (asprintf(&str, "%s %s", p->value, val) == -1)
45 return -ENOMEM;
46 } else
47 str = val ? strdup(val) : NULL;
48
49 free (p->value);
50 p->value = str;
51 return 0;
52 }
53 }
54 p = malloc(sizeof(*p));
55 if (!p)
56 return -ENOMEM;
57 p->next = vpninfo->script_env;
58 p->option = strdup(opt);
59 p->value = val ? (trunc ? strndup(val, trunc) : strdup(val)) : NULL;
60 vpninfo->script_env = p;
61 return 0;
62 }
63
script_setenv_int(struct openconnect_info * vpninfo,const char * opt,int value)64 int script_setenv_int(struct openconnect_info *vpninfo, const char *opt, int value)
65 {
66 char buf[16];
67 sprintf(buf, "%d", value);
68 return script_setenv(vpninfo, opt, buf, 0, 0);
69 }
70
netmasklen(struct in_addr addr)71 static int netmasklen(struct in_addr addr)
72 {
73 int masklen;
74
75 for (masklen = 0; masklen < 32; masklen++) {
76 if (ntohl(addr.s_addr) >= (0xffffffff << masklen))
77 break;
78 }
79 return 32 - masklen;
80 }
81
netmaskbits(int masklen)82 static uint32_t netmaskbits(int masklen)
83 {
84 if (masklen)
85 return htonl(0xffffffff << (32-masklen));
86 else /* Shifting by 32 is invalid, so special-case it */
87 return 0;
88 }
89
process_split_xxclude(struct openconnect_info * vpninfo,int include,const char * route,int * v4_incs,int * v6_incs)90 static int process_split_xxclude(struct openconnect_info *vpninfo,
91 int include, const char *route, int *v4_incs,
92 int *v6_incs)
93 {
94 struct in_addr net_addr, mask_addr;
95 const char *in_ex = include ? "IN" : "EX";
96 char envname[80], uptoslash[20];
97 const char *slash;
98 char *endp;
99 int masklen;
100
101 slash = strchr(route, '/');
102 envname[79] = uptoslash[19] = 0;
103
104 if (strchr(route, ':')) {
105 snprintf(envname, 79, "CISCO_IPV6_SPLIT_%sC_%d_ADDR", in_ex,
106 *v6_incs);
107 script_setenv(vpninfo, envname, route, slash ? slash - route : 0, 0);
108
109 snprintf(envname, 79, "CISCO_IPV6_SPLIT_%sC_%d_MASKLEN", in_ex,
110 *v6_incs);
111 script_setenv(vpninfo, envname, slash ? slash + 1 : "128", 0, 0);
112
113 (*v6_incs)++;
114 return 0;
115 }
116
117 if (!slash)
118 strncpy(uptoslash, route, sizeof(uptoslash)-1);
119 else {
120 int l = MIN(slash - route, sizeof(uptoslash)-1);
121 strncpy(uptoslash, route, l);
122 uptoslash[l] = 0;
123 }
124
125 /* Network address must be parseable */
126 if (!inet_aton(uptoslash, &net_addr)) {
127 bad:
128 if (include)
129 vpn_progress(vpninfo, PRG_ERR,
130 _("Discard bad split include: \"%s\"\n"),
131 route);
132 else
133 vpn_progress(vpninfo, PRG_ERR,
134 _("Discard bad split exclude: \"%s\"\n"),
135 route);
136 return -EINVAL;
137 }
138
139 /* Accept netmask in several forms */
140 if (!slash) {
141 /* no mask (same as /32) */
142 masklen = 32;
143 mask_addr.s_addr = netmaskbits(32);
144 } else if ((masklen = strtol(slash+1, &endp, 10))<=32 && *endp!='.') {
145 /* mask is /N */
146 mask_addr.s_addr = netmaskbits(masklen);
147 } else if (inet_aton(slash+1, &mask_addr)) {
148 /* mask is /A.B.C.D */
149 masklen = netmasklen(mask_addr);
150 /* something invalid like /255.0.0.1 */
151 if (netmaskbits(masklen) != mask_addr.s_addr)
152 goto bad;
153 } else
154 goto bad;
155
156 /* Fix incorrectly-set host bits */
157 if (net_addr.s_addr & ~mask_addr.s_addr) {
158 net_addr.s_addr &= mask_addr.s_addr;
159 if (include)
160 vpn_progress(vpninfo, PRG_ERR,
161 _("WARNING: Split include \"%s\" has host bits set, replacing with \"%s/%d\".\n"),
162 route, inet_ntoa(net_addr), masklen);
163 else
164 vpn_progress(vpninfo, PRG_ERR,
165 _("WARNING: Split exclude \"%s\" has host bits set, replacing with \"%s/%d\".\n"),
166 route, inet_ntoa(net_addr), masklen);
167 }
168
169 snprintf(envname, 79, "CISCO_SPLIT_%sC_%d_ADDR", in_ex, *v4_incs);
170 script_setenv(vpninfo, envname, inet_ntoa(net_addr), 0, 0);
171
172 snprintf(envname, 79, "CISCO_SPLIT_%sC_%d_MASK", in_ex, *v4_incs);
173 script_setenv(vpninfo, envname, inet_ntoa(mask_addr), 0, 0);
174
175 snprintf(envname, 79, "CISCO_SPLIT_%sC_%d_MASKLEN", in_ex, *v4_incs);
176 script_setenv_int(vpninfo, envname, masklen);
177
178 (*v4_incs)++;
179 return 0;
180 }
181
setenv_cstp_opts(struct openconnect_info * vpninfo)182 static void setenv_cstp_opts(struct openconnect_info *vpninfo)
183 {
184 char *env_buf;
185 int buflen = 0;
186 int bufofs = 0;
187 struct oc_vpn_option *opt;
188
189 for (opt = vpninfo->cstp_options; opt; opt = opt->next)
190 buflen += 2 + strlen(opt->option) + strlen(opt->value);
191
192 env_buf = malloc(buflen + 1);
193 if (!env_buf)
194 return;
195
196 env_buf[buflen] = 0;
197
198 for (opt = vpninfo->cstp_options; opt; opt = opt->next)
199 bufofs += snprintf(env_buf + bufofs, buflen - bufofs,
200 "%s=%s\n", opt->option, opt->value);
201
202 script_setenv(vpninfo, "CISCO_CSTP_OPTIONS", env_buf, 0, 0);
203 free(env_buf);
204 }
205
nybble(unsigned char n)206 static unsigned char nybble(unsigned char n)
207 {
208 if (n >= '0' && n <= '9') return n - '0';
209 else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
210 else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
211 return 0;
212 }
213
unhex(const char * data)214 unsigned char unhex(const char *data)
215 {
216 return (nybble(data[0]) << 4) | nybble(data[1]);
217 }
218
set_banner(struct openconnect_info * vpninfo)219 static void set_banner(struct openconnect_info *vpninfo)
220 {
221 char *banner, *legacy_banner, *q;
222 const char *p;
223
224 if (!vpninfo->banner || !(banner = malloc(strlen(vpninfo->banner)+1))) {
225 script_setenv(vpninfo, "CISCO_BANNER", NULL, 0, 0);
226 return;
227 }
228 p = vpninfo->banner;
229 q = banner;
230
231 while (*p) {
232 if (*p == '%' && isxdigit((int)(unsigned char)p[1]) &&
233 isxdigit((int)(unsigned char)p[2])) {
234 *(q++) = unhex(p + 1);
235 p += 3;
236 } else
237 *(q++) = *(p++);
238 }
239 *q = 0;
240 legacy_banner = openconnect_utf8_to_legacy(vpninfo, banner);
241 script_setenv(vpninfo, "CISCO_BANNER", legacy_banner, 0, 0);
242 if (legacy_banner != banner)
243 free(legacy_banner);
244
245 free(banner);
246 }
247
prepare_script_env(struct openconnect_info * vpninfo)248 void prepare_script_env(struct openconnect_info *vpninfo)
249 {
250 if (vpninfo->ip_info.gateway_addr)
251 script_setenv(vpninfo, "VPNGATEWAY", vpninfo->ip_info.gateway_addr, 0, 0);
252
253 set_banner(vpninfo);
254 script_setenv(vpninfo, "CISCO_SPLIT_INC", NULL, 0, 0);
255 script_setenv(vpninfo, "CISCO_SPLIT_EXC", NULL, 0, 0);
256
257 script_setenv_int(vpninfo, "INTERNAL_IP4_MTU", vpninfo->ip_info.mtu);
258
259 if (vpninfo->idle_timeout)
260 script_setenv_int(vpninfo, "IDLE_TIMEOUT", vpninfo->idle_timeout);
261 else
262 script_setenv(vpninfo, "IDLE_TIMEOUT", NULL, 0, 0);
263
264 if (vpninfo->ip_info.addr) {
265 script_setenv(vpninfo, "INTERNAL_IP4_ADDRESS", vpninfo->ip_info.addr, 0, 0);
266 if (vpninfo->ip_info.netmask) {
267 struct in_addr addr;
268 struct in_addr mask;
269
270 if (!inet_aton(vpninfo->ip_info.addr, &addr))
271 vpn_progress(vpninfo, PRG_ERR,
272 _("Ignoring legacy network because address \"%s\" is invalid.\n"),
273 vpninfo->ip_info.addr);
274 else if (!inet_aton(vpninfo->ip_info.netmask, &mask))
275 bad_netmask:
276 vpn_progress(vpninfo, PRG_ERR,
277 _("Ignoring legacy network because netmask \"%s\" is invalid.\n"),
278 vpninfo->ip_info.netmask);
279 else {
280 char *netaddr;
281 int masklen = netmasklen(mask);
282
283 if (netmaskbits(masklen) != mask.s_addr)
284 goto bad_netmask;
285 addr.s_addr &= mask.s_addr;
286 netaddr = inet_ntoa(addr);
287
288 script_setenv(vpninfo, "INTERNAL_IP4_NETADDR", netaddr, 0, 0);
289 script_setenv(vpninfo, "INTERNAL_IP4_NETMASK", vpninfo->ip_info.netmask, 0, 0);
290 script_setenv_int(vpninfo, "INTERNAL_IP4_NETMASKLEN", masklen);
291 }
292 }
293 }
294 if (vpninfo->ip_info.addr6) {
295 script_setenv(vpninfo, "INTERNAL_IP6_ADDRESS", vpninfo->ip_info.addr6, 0, 0);
296 script_setenv(vpninfo, "INTERNAL_IP6_NETMASK", vpninfo->ip_info.netmask6, 0, 0);
297 } else if (vpninfo->ip_info.netmask6) {
298 char *slash = strchr(vpninfo->ip_info.netmask6, '/');
299 script_setenv(vpninfo, "INTERNAL_IP6_NETMASK", vpninfo->ip_info.netmask6, 0, 0);
300 if (slash)
301 script_setenv(vpninfo, "INTERNAL_IP6_ADDRESS", vpninfo->ip_info.netmask6,
302 slash - vpninfo->ip_info.netmask6, 0);
303 }
304
305 if (vpninfo->ip_info.dns[0])
306 script_setenv(vpninfo, "INTERNAL_IP4_DNS", vpninfo->ip_info.dns[0], 0, 0);
307 else
308 script_setenv(vpninfo, "INTERNAL_IP4_DNS", NULL, 0, 0);
309 if (vpninfo->ip_info.dns[1])
310 script_setenv(vpninfo, "INTERNAL_IP4_DNS", vpninfo->ip_info.dns[1], 0, 1);
311 if (vpninfo->ip_info.dns[2])
312 script_setenv(vpninfo, "INTERNAL_IP4_DNS", vpninfo->ip_info.dns[2], 0, 1);
313
314 if (vpninfo->ip_info.nbns[0])
315 script_setenv(vpninfo, "INTERNAL_IP4_NBNS", vpninfo->ip_info.nbns[0], 0, 0);
316 else
317 script_setenv(vpninfo, "INTERNAL_IP4_NBNS", NULL, 0, 0);
318 if (vpninfo->ip_info.nbns[1])
319 script_setenv(vpninfo, "INTERNAL_IP4_NBNS", vpninfo->ip_info.nbns[1], 0, 1);
320 if (vpninfo->ip_info.nbns[2])
321 script_setenv(vpninfo, "INTERNAL_IP4_NBNS", vpninfo->ip_info.nbns[2], 0, 1);
322
323 if (vpninfo->ip_info.domain)
324 script_setenv(vpninfo, "CISCO_DEF_DOMAIN", vpninfo->ip_info.domain, 0, 0);
325 else
326 script_setenv(vpninfo, "CISCO_DEF_DOMAIN", NULL, 0, 0);
327
328 if (vpninfo->ip_info.proxy_pac)
329 script_setenv(vpninfo, "CISCO_PROXY_PAC", vpninfo->ip_info.proxy_pac, 0, 0);
330
331 if (vpninfo->ip_info.split_dns) {
332 char *list;
333 int len = 0;
334 struct oc_split_include *dns = vpninfo->ip_info.split_dns;
335
336 while (dns) {
337 len += strlen(dns->route) + 1;
338 dns = dns->next;
339 }
340 list = malloc(len);
341 if (list) {
342 char *p = list;
343
344 dns = vpninfo->ip_info.split_dns;
345 while (1) {
346 strcpy(p, dns->route);
347 p += strlen(p);
348 dns = dns->next;
349 if (!dns)
350 break;
351 *(p++) = ',';
352 }
353 script_setenv(vpninfo, "CISCO_SPLIT_DNS", list, 0, 0);
354 free(list);
355 }
356 }
357
358 if (vpninfo->ip_info.split_includes) {
359 struct oc_split_include *this = vpninfo->ip_info.split_includes;
360 int nr_split_includes = 0;
361 int nr_v6_split_includes = 0;
362
363 while (this) {
364 process_split_xxclude(vpninfo, 1, this->route,
365 &nr_split_includes,
366 &nr_v6_split_includes);
367 this = this->next;
368 }
369 if (nr_split_includes)
370 script_setenv_int(vpninfo, "CISCO_SPLIT_INC", nr_split_includes);
371 if (nr_v6_split_includes)
372 script_setenv_int(vpninfo, "CISCO_IPV6_SPLIT_INC", nr_v6_split_includes);
373 }
374
375 if (vpninfo->ip_info.split_excludes) {
376 struct oc_split_include *this = vpninfo->ip_info.split_excludes;
377 int nr_split_excludes = 0;
378 int nr_v6_split_excludes = 0;
379
380 while (this) {
381 process_split_xxclude(vpninfo, 0, this->route,
382 &nr_split_excludes,
383 &nr_v6_split_excludes);
384 this = this->next;
385 }
386 if (nr_split_excludes)
387 script_setenv_int(vpninfo, "CISCO_SPLIT_EXC", nr_split_excludes);
388 if (nr_v6_split_excludes)
389 script_setenv_int(vpninfo, "CISCO_IPV6_SPLIT_EXC", nr_v6_split_excludes);
390 }
391 setenv_cstp_opts(vpninfo);
392 }
393
free_split_routes(struct openconnect_info * vpninfo)394 void free_split_routes(struct openconnect_info *vpninfo)
395 {
396 struct oc_split_include *inc;
397
398 for (inc = vpninfo->ip_info.split_includes; inc; ) {
399 struct oc_split_include *next = inc->next;
400 free(inc);
401 inc = next;
402 }
403 for (inc = vpninfo->ip_info.split_excludes; inc; ) {
404 struct oc_split_include *next = inc->next;
405 free(inc);
406 inc = next;
407 }
408 for (inc = vpninfo->ip_info.split_dns; inc; ) {
409 struct oc_split_include *next = inc->next;
410 free(inc);
411 inc = next;
412 }
413 vpninfo->ip_info.split_dns = vpninfo->ip_info.split_includes =
414 vpninfo->ip_info.split_excludes = NULL;
415 }
416
417
418 #ifdef _WIN32
create_script_env(struct openconnect_info * vpninfo)419 static wchar_t *create_script_env(struct openconnect_info *vpninfo)
420 {
421 struct oc_vpn_option *opt;
422 struct oc_text_buf *envbuf;
423 wchar_t **oldenv, **p, *newenv = NULL;
424 int nr_envs = 0, i;
425
426 /* _wenviron is NULL until we call _wgetenv() */
427 (void)_wgetenv(L"PATH");
428
429 /* Take a copy of _wenviron (but not of its strings) */
430 for (p = _wenviron; *p; p++)
431 nr_envs++;
432
433 oldenv = malloc(nr_envs * sizeof(*oldenv));
434 if (!oldenv)
435 return NULL;
436 memcpy(oldenv, _wenviron, nr_envs * sizeof(*oldenv));
437
438 envbuf = buf_alloc();
439
440 /* Add the script environment variables, prodding out any members of
441 oldenv which are obsoleted by them. */
442 for (opt = vpninfo->script_env; opt && !buf_error(envbuf); opt = opt->next) {
443 struct oc_text_buf *buf;
444
445 buf = buf_alloc();
446 buf_append_utf16le(buf, opt->option);
447 buf_append_utf16le(buf, "=");
448
449 if (buf_error(buf)) {
450 buf_free(buf);
451 goto err;
452 }
453
454 /* See if we can find it in the existing environment */
455 for (i = 0; i < nr_envs; i++) {
456 if (oldenv[i] &&
457 !wcsncmp((wchar_t *)buf->data, oldenv[i], buf->pos / 2)) {
458 oldenv[i] = NULL;
459 break;
460 }
461 }
462
463 if (opt->value) {
464 buf_append_bytes(envbuf, buf->data, buf->pos);
465 buf_append_utf16le(envbuf, opt->value);
466 buf_append_bytes(envbuf, "\0\0", 2);
467 }
468
469 buf_free(buf);
470 }
471
472 for (i = 0; i < nr_envs && !buf_error(envbuf); i++) {
473 if (oldenv[i])
474 buf_append_bytes(envbuf, oldenv[i],
475 (wcslen(oldenv[i]) + 1) * sizeof(wchar_t));
476 }
477
478 buf_append_bytes(envbuf, "\0\0", 2);
479
480 if (!buf_error(envbuf)) {
481 newenv = (wchar_t *)envbuf->data;
482 envbuf->data = NULL;
483 }
484
485 err:
486 free(oldenv);
487 buf_free(envbuf);
488 return newenv;
489 }
490
script_config_tun(struct openconnect_info * vpninfo,const char * reason)491 int script_config_tun(struct openconnect_info *vpninfo, const char *reason)
492 {
493 wchar_t *script_w;
494 wchar_t *script_env;
495 int nr_chars;
496 int ret;
497 char *cmd;
498 PROCESS_INFORMATION pi;
499 STARTUPINFOW si;
500 DWORD cpflags;
501
502 if (!vpninfo->vpnc_script || vpninfo->script_tun)
503 return 0;
504
505 memset(&si, 0, sizeof(si));
506 si.cb = sizeof(si);
507 /* probably superfluous */
508 si.dwFlags = STARTF_USESHOWWINDOW;
509 si.wShowWindow = SW_HIDE;
510
511 script_setenv(vpninfo, "reason", reason, 0, 0);
512
513 if (asprintf(&cmd, "cscript.exe \"%s\"", vpninfo->vpnc_script) == -1)
514 return 0;
515
516 nr_chars = MultiByteToWideChar(CP_UTF8, 0, cmd, -1, NULL, 0);
517 script_w = malloc(nr_chars * sizeof(wchar_t));
518
519 if (!script_w) {
520 free(cmd);
521 return -ENOMEM;
522 }
523
524 MultiByteToWideChar(CP_UTF8, 0, cmd, -1, script_w, nr_chars);
525
526 free(cmd);
527
528 script_env = create_script_env(vpninfo);
529
530 cpflags = CREATE_UNICODE_ENVIRONMENT;
531 /* If we're running from a console, let the script use it too. */
532 if (!GetConsoleWindow())
533 cpflags |= CREATE_NO_WINDOW;
534
535 if (CreateProcessW(NULL, script_w, NULL, NULL, FALSE, cpflags,
536 script_env, NULL, &si, &pi)) {
537 ret = WaitForSingleObject(pi.hProcess,10000);
538 CloseHandle(pi.hThread);
539 CloseHandle(pi.hProcess);
540 if (ret == WAIT_TIMEOUT)
541 ret = -ETIMEDOUT;
542 else
543 ret = 0;
544 } else {
545 ret = -EIO;
546 }
547
548 free(script_env);
549
550 if (ret < 0) {
551 char *errstr = openconnect__win32_strerror(GetLastError());
552 vpn_progress(vpninfo, PRG_ERR,
553 _("Failed to spawn script '%s' for %s: %s\n"),
554 vpninfo->vpnc_script, reason, errstr);
555 free(errstr);
556 goto cleanup;
557 }
558
559 cleanup:
560 free(script_w);
561 return ret;
562 }
563 #else
564 /* Must only be run after fork(). */
apply_script_env(struct oc_vpn_option * envs)565 int apply_script_env(struct oc_vpn_option *envs)
566 {
567 struct oc_vpn_option *p;
568
569 for (p = envs; p; p = p->next) {
570 if (p->value)
571 setenv(p->option, p->value, 1);
572 else
573 unsetenv(p->option);
574 }
575 return 0;
576 }
577
script_config_tun(struct openconnect_info * vpninfo,const char * reason)578 int script_config_tun(struct openconnect_info *vpninfo, const char *reason)
579 {
580 int ret;
581 pid_t pid;
582
583 if (!vpninfo->vpnc_script || vpninfo->script_tun)
584 return 0;
585
586 pid = fork();
587 if (!pid) {
588 /* Child */
589 char *script = openconnect_utf8_to_legacy(vpninfo, vpninfo->vpnc_script);
590
591 apply_script_env(vpninfo->script_env);
592
593 setenv("reason", reason, 1);
594
595 execl("/bin/sh", "/bin/sh", "-c", script, NULL);
596 exit(127);
597 }
598 if (pid == -1 || waitpid(pid, &ret, 0) == -1) {
599 int e = errno;
600 vpn_progress(vpninfo, PRG_ERR,
601 _("Failed to spawn script '%s' for %s: %s\n"),
602 vpninfo->vpnc_script, reason, strerror(e));
603 return -e;
604 }
605
606 if (!WIFEXITED(ret)) {
607 vpn_progress(vpninfo, PRG_ERR,
608 _("Script '%s' exited abnormally (%x)\n"),
609 vpninfo->vpnc_script, ret);
610 return -EIO;
611 }
612
613 ret = WEXITSTATUS(ret);
614 if (ret) {
615 vpn_progress(vpninfo, PRG_ERR,
616 _("Script '%s' returned error %d\n"),
617 vpninfo->vpnc_script, ret);
618 return -EIO;
619 }
620 return 0;
621 }
622 #endif
623