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