Add cancellation handling to proxy I/O functions
[platform/upstream/openconnect.git] / main.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 #ifdef ANDROID
28 #include <android/log.h>
29 #else
30 #include <syslog.h>
31 #endif
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <signal.h>
35 #include <string.h>
36 #ifdef HAVE_STRINGS_H
37 #include <strings.h>
38 #endif
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <pwd.h>
43 #include <sys/utsname.h>
44 #include <sys/types.h>
45 #include <openssl/ui.h>
46 #ifdef LIBPROXY_HDR
47 #include LIBPROXY_HDR
48 #endif
49
50 #include <getopt.h>
51
52 #include "openconnect-internal.h"
53
54 static int write_new_config(void *_vpninfo,
55                             char *buf, int buflen);
56 static void write_progress(void *_vpninfo,
57                            int level, const char *fmt, ...);
58 static void syslog_progress(void *_vpninfo,
59                             int level, const char *fmt, ...);
60 static int validate_peer_cert(void *_vpninfo,
61                               X509 *peer_cert, const char *reason);
62
63 /* A sanity check that the openconnect executable is running against a
64    library of the same version */
65 #define openconnect_version openconnect_binary_version
66 #include "version.c"
67 #undef openconnect_version
68
69 int verbose = PRG_INFO;
70 int background;
71 int do_passphrase_from_fsid;
72 int nocertcheck;
73 int non_inter;
74
75 enum {
76         OPT_AUTHGROUP = 0x100,
77         OPT_CAFILE,
78         OPT_CONFIGFILE,
79         OPT_COOKIEONLY,
80         OPT_COOKIE_ON_STDIN,
81         OPT_CSD_USER,
82         OPT_CSD_WRAPPER,
83         OPT_DISABLE_IPV6,
84         OPT_DTLS_CIPHERS,
85         OPT_FORCE_DPD,
86         OPT_KEY_PASSWORD_FROM_FSID,
87         OPT_LIBPROXY,
88         OPT_NO_CERT_CHECK,
89         OPT_NO_DTLS,
90         OPT_NO_HTTP_KEEPALIVE,
91         OPT_NO_PASSWD,
92         OPT_NO_PROXY,
93         OPT_PIDFILE,
94         OPT_PASSWORD_ON_STDIN,
95         OPT_PRINTCOOKIE,
96         OPT_RECONNECT_TIMEOUT,
97         OPT_SERVERCERT,
98         OPT_USERAGENT,
99         OPT_NON_INTER,
100 };
101
102 #ifdef __sun__
103 /*
104  * The 'name' field in Solaris 'struct option' lacks the 'const', and causes
105  * lots of warnings unless we cast it... https://www.illumos.org/issues/1881
106 */
107 #define OPTION(name, arg, abbrev) {(char *)name, arg, NULL, abbrev}
108 #else
109 #define OPTION(name, arg, abbrev) {name, arg, NULL, abbrev}
110 #endif
111
112 static struct option long_options[] = {
113         OPTION("background", 0, 'b'),
114         OPTION("pid-file", 1, OPT_PIDFILE),
115         OPTION("certificate", 1, 'c'),
116         OPTION("sslkey", 1, 'k'),
117         OPTION("cookie", 1, 'C'),
118         OPTION("deflate", 0, 'd'),
119         OPTION("no-deflate", 0, 'D'),
120         OPTION("cert-expire-warning", 1, 'e'),
121         OPTION("usergroup", 1, 'g'),
122         OPTION("help", 0, 'h'),
123         OPTION("interface", 1, 'i'),
124         OPTION("mtu", 1, 'm'),
125         OPTION("setuid", 1, 'U'),
126         OPTION("script", 1, 's'),
127         OPTION("script-tun", 0, 'S'),
128         OPTION("syslog", 0, 'l'),
129         OPTION("key-type", 1, 'K'),
130         OPTION("key-password", 1, 'p'),
131         OPTION("proxy", 1, 'P'),
132         OPTION("user", 1, 'u'),
133         OPTION("verbose", 0, 'v'),
134         OPTION("version", 0, 'V'),
135         OPTION("cafile", 1, OPT_CAFILE),
136         OPTION("config", 1, OPT_CONFIGFILE),
137         OPTION("no-dtls", 0, OPT_NO_DTLS),
138         OPTION("cookieonly", 0, OPT_COOKIEONLY),
139         OPTION("printcookie", 0, OPT_PRINTCOOKIE),
140         OPTION("quiet", 0, 'q'),
141         OPTION("queue-len", 1, 'Q'),
142         OPTION("xmlconfig", 1, 'x'),
143         OPTION("cookie-on-stdin", 0, OPT_COOKIE_ON_STDIN),
144         OPTION("passwd-on-stdin", 0, OPT_PASSWORD_ON_STDIN),
145         OPTION("no-passwd", 0, OPT_NO_PASSWD),
146         OPTION("reconnect-timeout", 1, OPT_RECONNECT_TIMEOUT),
147         OPTION("dtls-ciphers", 1, OPT_DTLS_CIPHERS),
148         OPTION("authgroup", 1, OPT_AUTHGROUP),
149         OPTION("servercert", 1, OPT_SERVERCERT),
150         OPTION("key-password-from-fsid", 0, OPT_KEY_PASSWORD_FROM_FSID),
151         OPTION("useragent", 1, OPT_USERAGENT),
152         OPTION("csd-user", 1, OPT_CSD_USER),
153         OPTION("csd-wrapper", 1, OPT_CSD_WRAPPER),
154         OPTION("disable-ipv6", 0, OPT_DISABLE_IPV6),
155         OPTION("no-proxy", 0, OPT_NO_PROXY),
156         OPTION("libproxy", 0, OPT_LIBPROXY),
157         OPTION("no-http-keepalive", 0, OPT_NO_HTTP_KEEPALIVE),
158         OPTION("no-cert-check", 0, OPT_NO_CERT_CHECK),
159         OPTION("force-dpd", 1, OPT_FORCE_DPD),
160         OPTION("non-inter", 0, OPT_NON_INTER),
161         OPTION(NULL, 0, 0)
162 };
163
164 static void helpmessage(void)
165 {
166         printf(_("For assistance with OpenConnect, please see the web page at\n"
167                  "  http://www.infradead.org/openconnect/mail.html\n"));
168 }
169
170 static void usage(void)
171 {
172         printf(_("Usage:  openconnect [options] <server>\n"));
173         printf(_("Open client for Cisco AnyConnect VPN, version %s\n\n"), openconnect_version);
174         printf("      --config=CONFIGFILE         %s\n", _("Read options from config file"));
175         printf("  -b, --background                %s\n", _("Continue in background after startup"));
176         printf("      --pid-file=PIDFILE          %s\n", _("Write the daemons pid to this file"));
177         printf("  -c, --certificate=CERT          %s\n", _("Use SSL client certificate CERT"));
178         printf("  -e, --cert-expire-warning=DAYS  %s\n", _("Warn when certificate lifetime < DAYS"));
179         printf("  -k, --sslkey=KEY                %s\n", _("Use SSL private key file KEY"));
180         printf("  -K, --key-type=TYPE             %s\n", _("Private key type (PKCS#12 / TPM / PEM)"));
181         printf("  -C, --cookie=COOKIE             %s\n", _("Use WebVPN cookie COOKIE"));
182         printf("      --cookie-on-stdin           %s\n", _("Read cookie from standard input"));
183         printf("  -d, --deflate                   %s\n", _("Enable compression (default)"));
184         printf("  -D, --no-deflate                %s\n", _("Disable compression"));
185         printf("      --force-dpd=INTERVAL        %s\n", _("Set minimum Dead Peer Detection interval"));
186         printf("  -g, --usergroup=GROUP           %s\n", _("Set login usergroup"));
187         printf("  -h, --help                      %s\n", _("Display help text"));
188         printf("  -i, --interface=IFNAME          %s\n", _("Use IFNAME for tunnel interface"));
189         printf("  -l, --syslog                    %s\n", _("Use syslog for progress messages"));
190         printf("  -U, --setuid=USER               %s\n", _("Drop privileges after connecting"));
191         printf("      --csd-user=USER             %s\n", _("Drop privileges during CSD execution"));
192         printf("      --csd-wrapper=SCRIPT        %s\n", _("Run SCRIPT instead of CSD binary"));
193         printf("  -m, --mtu=MTU                   %s\n", _("Request MTU from server"));
194         printf("  -p, --key-password=PASS         %s\n", _("Set key passphrase or TPM SRK PIN"));
195         printf("      --key-password-from-fsid    %s\n", _("Key passphrase is fsid of file system"));
196         printf("  -P, --proxy=URL                 %s\n", _("Set proxy server"));
197         printf("      --no-proxy                  %s\n", _("Disable proxy"));
198         printf("      --libproxy                  %s\n", _("Use libproxy to automatically configure proxy"));
199 #ifndef LIBPROXY_HDR
200         printf("                                  %s\n", _("(NOTE: libproxy disabled in this build)"));
201 #endif
202         printf("  -q, --quiet                     %s\n", _("Less output"));
203         printf("  -Q, --queue-len=LEN             %s\n", _("Set packet queue limit to LEN pkts"));
204         printf("  -s, --script=SCRIPT             %s\n", _("Shell command line for using a vpnc-compatible config script"));
205         printf("                                  %s: \"%s\"\n", _("default"), DEFAULT_VPNCSCRIPT);
206         printf("  -S, --script-tun                %s\n", _("Pass traffic to 'script' program, not tun"));
207         printf("  -u, --user=NAME                 %s\n", _("Set login username"));
208         printf("  -V, --version                   %s\n", _("Report version number"));
209         printf("  -v, --verbose                   %s\n", _("More output"));
210         printf("  -x, --xmlconfig=CONFIG          %s\n", _("XML config file"));
211         printf("      --authgroup=GROUP           %s\n", _("Choose authentication login selection"));
212         printf("      --cookieonly                %s\n", _("Fetch webvpn cookie only; don't connect"));
213         printf("      --printcookie               %s\n", _("Print webvpn cookie before connecting"));
214         printf("      --cafile=FILE               %s\n", _("Cert file for server verification"));
215         printf("      --disable-ipv6              %s\n", _("Do not ask for IPv6 connectivity"));
216         printf("      --dtls-ciphers=LIST         %s\n", _("OpenSSL ciphers to support for DTLS"));
217         printf("      --no-dtls                   %s\n", _("Disable DTLS"));
218         printf("      --no-http-keepalive         %s\n", _("Disable HTTP connection re-use"));
219         printf("      --no-passwd                 %s\n", _("Disable password/SecurID authentication"));
220         printf("      --no-cert-check             %s\n", _("Do not require server SSL cert to be valid"));
221         printf("      --non-inter                 %s\n", _("Do not expect user input; exit if it is required"));
222         printf("      --passwd-on-stdin           %s\n", _("Read password from standard input"));
223         printf("      --reconnect-timeout         %s\n", _("Connection retry timeout in seconds"));
224         printf("      --servercert=FINGERPRINT    %s\n", _("Server's certificate SHA1 fingerprint"));
225         printf("      --useragent=STRING          %s\n", _("HTTP header User-Agent: field"));
226         printf("\n");
227
228         helpmessage();
229         exit(1);
230 }
231
232 static void read_stdin(char **string)
233 {
234         char *c = malloc(100);
235         if (!c) {
236                 fprintf(stderr, _("Allocation failure for string from stdin\n"));
237                 exit(1);
238         }
239         if (!fgets(c, 100, stdin)) {
240                 perror(_("fgets (stdin)"));
241                 exit(1);
242         }
243
244         *string = c;
245
246         c = strchr(*string, '\n');
247         if (c)
248                 *c = 0;
249 }
250 static void handle_sigusr(int sig)
251 {
252         if (sig == SIGUSR1)
253                 verbose = PRG_TRACE;
254         else if (sig == SIGUSR2)
255                 verbose = PRG_INFO;
256 }
257
258 static FILE *config_file = NULL;
259 static int config_line_num = 0;
260
261 #define keep_config_arg() (config_arg)
262
263 static int next_option(int argc, char **argv, char **config_arg)
264 {
265         /* These get re-used */
266         static char *line_buf = NULL;
267         static size_t line_size = 0;
268
269         ssize_t llen;
270         int opt, optlen;
271         struct option *this;
272         char *line;
273         int ate_equals = 0;
274
275  next:
276         if (!config_file) {
277                 opt = getopt_long(argc, argv,
278                                   "bC:c:e:Ddg:hi:k:K:lpP:Q:qSs:U:u:Vvx:",
279                                   long_options, NULL);
280
281                 *config_arg = optarg;
282                 return opt;
283         }
284
285         llen = getline(&line_buf, &line_size, config_file);
286         if (llen < 0) {
287                 if (feof(config_file)) {
288                         fclose(config_file);
289                         config_file = NULL;
290                         goto next;
291                 }
292                 fprintf(stderr, _("Failed to get line from config file: %s\n"),
293                         strerror(errno));
294                 exit(1);
295         }
296         line = line_buf;
297
298         /* Strip the trailing newline (coping with DOS newlines) */
299         if (llen && line[llen-1] == '\n')
300                 line[--llen] = 0;
301         if (llen && line[llen-1] == '\r')
302                 line[--llen] = 0;
303
304         /* Skip and leading whitespace */
305         while (line[0] == ' ' || line[0] == '\t' || line[0] == '\r')
306                 line++;
307
308         /* Ignore comments and empty lines */
309         if (!line[0] || line[0] == '#') {
310                 config_line_num++;
311                 goto next;
312         }
313
314         /* Try to match on a known option... naïvely. This could be improved. */
315         for (this = long_options; this->name; this++) {
316                 optlen = strlen(this->name);
317                 /* If the option isn't followed by whitespace or NUL, or
318                    perhaps an equals sign if the option takes an argument,
319                    then it's not a match */
320                 if (!strncmp(this->name, line, optlen) &&
321                     (!line[optlen] || line[optlen] == ' ' || line[optlen] == '\t' ||
322                      line[optlen] == '='))
323                     break;
324         }
325         if (!this->name) {
326                 char *l;
327
328                 for (l = line; *l && *l != ' ' && *l != '\t'; l++)
329                         ;
330
331                 *l = 0;
332                 fprintf(stderr, _("Unrecognised option at line %d: '%s'\n"),
333                         config_line_num, line);
334                 return '?';
335         }
336         line += optlen;
337         while (*line == ' ' || *line == '\t' ||
338                (*line == '=' && this->has_arg && !ate_equals && ++ate_equals))
339                 line++;
340
341         if (!this->has_arg && *line) {
342                 fprintf(stderr, _("Option '%s' does not take an argument at line %d\n"),
343                         this->name, config_line_num);
344                 return '?';
345         } else if (this->has_arg && !*line) {
346                 fprintf(stderr, _("Option '%s' requires an argument at line %d\n"),
347                         this->name, config_line_num);
348                 return '?';
349         }
350
351         config_line_num++;
352         *config_arg = line;
353         return this->val;
354
355 }
356
357 int main(int argc, char **argv)
358 {
359         struct openconnect_info *vpninfo;
360         struct utsname utsbuf;
361         struct sigaction sa;
362         int cookieonly = 0;
363         int use_syslog = 0;
364         char *proxy = getenv("https_proxy");
365         int autoproxy = 0;
366         uid_t uid = getuid();
367         int opt;
368         char *pidfile = NULL;
369         FILE *fp = NULL;
370         char *config_arg;
371
372 #ifdef ENABLE_NLS
373         bindtextdomain("openconnect", LOCALEDIR);
374         setlocale(LC_ALL, "");
375 #endif
376
377         if (strcmp(openconnect_version, openconnect_binary_version)) {
378                 fprintf(stderr, _("WARNING: This version of openconnect is %s but\n"
379                                   "         the libopenconnect library is %s\n"),
380                         openconnect_binary_version, openconnect_version);
381         }
382                         
383         openconnect_init_openssl();
384
385         vpninfo = malloc(sizeof(*vpninfo));
386         if (!vpninfo) {
387                 fprintf(stderr, _("Failed to allocate vpninfo structure\n"));
388                 exit(1);
389         }
390         memset(vpninfo, 0, sizeof(*vpninfo));
391
392         /* Set up some defaults */
393         vpninfo->tun_fd = vpninfo->ssl_fd = vpninfo->dtls_fd = vpninfo->new_dtls_fd = -1;
394         vpninfo->useragent = openconnect_create_useragent("Open AnyConnect VPN Agent");
395         vpninfo->mtu = 1406;
396         vpninfo->deflate = 1;
397         vpninfo->dtls_attempt_period = 60;
398         vpninfo->max_qlen = 10;
399         vpninfo->reconnect_interval = RECONNECT_INTERVAL_MIN;
400         vpninfo->reconnect_timeout = 300;
401         vpninfo->uid_csd = 0;
402         vpninfo->uid_csd_given = 0;
403         vpninfo->validate_peer_cert = validate_peer_cert;
404         vpninfo->cbdata = vpninfo;
405         vpninfo->cert_expire_warning = 60 * 86400;
406         vpninfo->vpnc_script = DEFAULT_VPNCSCRIPT;
407         vpninfo->cancel_fd = -1;
408
409         if (!uname(&utsbuf))
410                 vpninfo->localname = utsbuf.nodename;
411         else
412                 vpninfo->localname = "localhost";
413
414         while ((opt = next_option(argc, argv, &config_arg))) {
415
416                 if (opt < 0)
417                         break;
418
419                 switch (opt) {
420                 case OPT_CONFIGFILE:
421                         if (config_file) {
422                                 fprintf(stderr, _("Cannot use 'config' option inside config file\n"));
423                                 exit(1);
424                         }
425                         config_file = fopen(config_arg, "r");
426                         if (!config_file) {
427                                 fprintf(stderr, _("Cannot open config file '%s': %s\n"),
428                                         config_arg, strerror(errno));
429                                 exit(1);
430                         }
431                         config_line_num = 1;
432                         /* The next option will come from the file... */
433                         break;
434                 case OPT_CAFILE:
435                         vpninfo->cafile = keep_config_arg();
436                         break;
437                 case OPT_PIDFILE:
438                         pidfile = keep_config_arg();
439                         break;
440                 case OPT_SERVERCERT:
441                         vpninfo->servercert = keep_config_arg();
442                         break;
443                 case OPT_NO_DTLS:
444                         vpninfo->dtls_attempt_period = 0;
445                         break;
446                 case OPT_COOKIEONLY:
447                         cookieonly = 1;
448                         break;
449                 case OPT_PRINTCOOKIE:
450                         cookieonly = 2;
451                         break;
452                 case OPT_COOKIE_ON_STDIN:
453                         read_stdin(&vpninfo->cookie);
454                         /* If the cookie is empty, ignore it */
455                         if (! *vpninfo->cookie) {
456                                 vpninfo->cookie = NULL;
457                         }
458                         break;
459                 case OPT_PASSWORD_ON_STDIN:
460                         read_stdin(&vpninfo->password);
461                         break;
462                 case OPT_NO_PASSWD:
463                         vpninfo->nopasswd = 1;
464                         break;
465                 case OPT_NON_INTER:
466                         non_inter = 1;
467                         vpninfo->nopasswd = 1;
468                         break;
469                 case OPT_RECONNECT_TIMEOUT:
470                         vpninfo->reconnect_timeout = atoi(config_arg);
471                         break;
472                 case OPT_DTLS_CIPHERS:
473                         vpninfo->dtls_ciphers = keep_config_arg();
474                         break;
475                 case OPT_AUTHGROUP:
476                         vpninfo->authgroup = keep_config_arg();
477                         break;
478                 case 'b':
479                         background = 1;
480                         break;
481                 case 'C':
482                         vpninfo->cookie = keep_config_arg();
483                         break;
484                 case 'c':
485                         vpninfo->cert = keep_config_arg();
486                         break;
487                 case 'e':
488                         vpninfo->cert_expire_warning = 86400 * atoi(config_arg);
489                         break;
490                 case 'k':
491                         vpninfo->sslkey = keep_config_arg();
492                         break;
493                 case 'K':
494                         if (!strcasecmp(config_arg, "PKCS#12") ||
495                             !strcasecmp(config_arg, "PKCS12")) {
496                                 vpninfo->cert_type = CERT_TYPE_PKCS12;
497                         } else if (!strcasecmp(config_arg, "TPM")) {
498                                 vpninfo->cert_type = CERT_TYPE_TPM;
499                         } else if (!strcasecmp(config_arg, "PEM")) {
500                                 vpninfo->cert_type = CERT_TYPE_PEM;
501                         } else {
502                                 fprintf(stderr, _("Unknown certificate type '%s'\n"),
503                                         config_arg);
504                                 usage();
505                         }
506                 case 'd':
507                         vpninfo->deflate = 1;
508                         break;
509                 case 'D':
510                         vpninfo->deflate = 0;
511                         break;
512                 case 'g':
513                         free(vpninfo->urlpath);
514                         vpninfo->urlpath = strdup(config_arg);
515                         break;
516                 case 'h':
517                         usage();
518                 case 'i':
519                         vpninfo->ifname = keep_config_arg();
520                         break;
521                 case 'l':
522                         use_syslog = 1;
523                         break;
524                 case 'm':
525                         vpninfo->mtu = atol(config_arg);
526                         if (vpninfo->mtu < 576) {
527                                 fprintf(stderr, _("MTU %d too small\n"), vpninfo->mtu);
528                                 vpninfo->mtu = 576;
529                         }
530                         break;
531                 case 'p':
532                         vpninfo->cert_password = keep_config_arg();
533                         break;
534                 case 'P': 
535                         proxy = keep_config_arg();
536                         autoproxy = 0;
537                         break;
538                 case OPT_NO_PROXY:
539                         autoproxy = 0;
540                         proxy = NULL;
541                 case OPT_LIBPROXY:
542                         autoproxy = 1;
543                         proxy = NULL;
544                         break;
545                 case OPT_NO_HTTP_KEEPALIVE:
546                         fprintf(stderr,
547                                 _("Disabling all HTTP connection re-use due to --no-http-keepalive option.\n"
548                                   "If this helps, please report to <openconnect-devel@lists.infradead.org>.\n"));
549                         vpninfo->no_http_keepalive = 1;
550                         break;
551                 case OPT_NO_CERT_CHECK:
552                         nocertcheck = 1;
553                         break;
554                 case 's':
555                         vpninfo->vpnc_script = keep_config_arg();
556                         break;
557                 case 'S':
558                         vpninfo->script_tun = 1;
559                         break;
560                 case 'u':
561                         vpninfo->username = keep_config_arg();
562                         break;
563                 case 'U': {
564                         char *strend;
565                         uid = strtol(config_arg, &strend, 0);
566                         if (strend[0]) {
567                                 struct passwd *pw = getpwnam(config_arg);
568                                 if (!pw) {
569                                         fprintf(stderr, _("Invalid user \"%s\"\n"),
570                                                 config_arg);
571                                         exit(1);
572                                 }
573                                 uid = pw->pw_uid;
574                         }
575                         break;
576                 }
577                 case OPT_CSD_USER: {
578                         char *strend;
579                         vpninfo->uid_csd = strtol(config_arg, &strend, 0);
580                         if (strend[0]) {
581                                 struct passwd *pw = getpwnam(config_arg);
582                                 if (!pw) {
583                                         fprintf(stderr, _("Invalid user \"%s\"\n"),
584                                                 config_arg);
585                                         exit(1);
586                                 }
587                                 vpninfo->uid_csd = pw->pw_uid;
588                         }
589                         vpninfo->uid_csd_given = 1;
590                         break;
591                 }
592                 case OPT_CSD_WRAPPER:
593                         vpninfo->csd_wrapper = keep_config_arg();
594                         break;
595                 case OPT_DISABLE_IPV6:
596                         vpninfo->disable_ipv6 = 1;
597                         break;
598                 case 'Q':
599                         vpninfo->max_qlen = atol(config_arg);
600                         if (!vpninfo->max_qlen) {
601                                 fprintf(stderr, _("Queue length zero not permitted; using 1\n"));
602                                 vpninfo->max_qlen = 1;
603                         }
604                         break;
605                 case 'q':
606                         verbose = PRG_ERR;
607                         break;
608                 case 'v':
609                         verbose = PRG_TRACE;
610                         break;
611                 case 'V':
612                         printf(_("OpenConnect version %s\n"), openconnect_version);
613                         exit(0);
614                 case 'x':
615                         vpninfo->xmlconfig = keep_config_arg();
616                         vpninfo->write_new_config = write_new_config;
617                         break;
618                 case OPT_KEY_PASSWORD_FROM_FSID:
619                         do_passphrase_from_fsid = 1;
620                         break;
621                 case OPT_USERAGENT:
622                         free(vpninfo->useragent);
623                         vpninfo->useragent = strdup(config_arg);
624                         break;
625                 case OPT_FORCE_DPD:
626                         vpninfo->dtls_times.dpd = vpninfo->ssl_times.dpd = atoi(config_arg);
627                         break;
628                 default:
629                         usage();
630                 }
631         }
632
633         if (optind != argc - 1) {
634                 fprintf(stderr, _("No server specified\n"));
635                 usage();
636         }
637
638         if (!vpninfo->sslkey)
639                 vpninfo->sslkey = vpninfo->cert;
640
641         vpninfo->progress = write_progress;
642
643         if (autoproxy) {
644 #ifdef LIBPROXY_HDR
645                 vpninfo->proxy_factory = px_proxy_factory_new();
646 #else
647                 fprintf(stderr, _("This version of openconnect was built without libproxy support\n"));
648                 exit(1);
649 #endif
650         }
651
652         if (proxy && openconnect_set_http_proxy(vpninfo, strdup(proxy)))
653                 exit(1);
654
655         if (use_syslog) {
656 #ifndef ANDROID
657                 openlog("openconnect", LOG_PID, LOG_DAEMON);
658 #endif
659                 vpninfo->progress = syslog_progress;
660         }
661
662         memset(&sa, 0, sizeof(sa));
663         sa.sa_handler = handle_sigusr;
664
665         sigaction(SIGUSR1, &sa, NULL);
666         sigaction(SIGUSR2, &sa, NULL);
667
668         if (vpninfo->sslkey && do_passphrase_from_fsid)
669                 openconnect_passphrase_from_fsid(vpninfo);
670
671         if (config_lookup_host(vpninfo, argv[optind]))
672                 exit(1);
673
674         if (!vpninfo->hostname) {
675                 char *url = strdup(argv[optind]);
676                 char *scheme;
677                 char *group;
678
679                 if (internal_parse_url(url, &scheme, &vpninfo->hostname, &vpninfo->port,
680                               &group, 443)) {
681                         fprintf(stderr, _("Failed to parse server URL '%s'\n"),
682                                 url);
683                         exit(1);
684                 }
685                 if (scheme && strcmp(scheme, "https")) {
686                         fprintf(stderr, _("Only https:// permitted for server URL\n"));
687                         exit(1);
688                 }
689                 if (group) {
690                         free(vpninfo->urlpath);
691                         vpninfo->urlpath = group;
692                 }
693                 free(scheme);
694                 free(url);
695         }
696
697 #ifdef SSL_UI
698         set_openssl_ui();
699 #endif
700
701         if (!vpninfo->cookie && openconnect_obtain_cookie(vpninfo)) {
702                 fprintf(stderr, _("Failed to obtain WebVPN cookie\n"));
703                 exit(1);
704         }
705
706         if (cookieonly) {
707                 printf("%s\n", vpninfo->cookie);
708                 if (cookieonly == 1)
709                         /* We use cookieonly=2 for 'print it and continue' */
710                         exit(0);
711         }
712         if (make_cstp_connection(vpninfo)) {
713                 fprintf(stderr, _("Creating SSL connection failed\n"));
714                 exit(1);
715         }
716
717         if (setup_tun(vpninfo)) {
718                 fprintf(stderr, _("Set up tun device failed\n"));
719                 exit(1);
720         }
721
722         if (uid != getuid()) {
723                 if (setuid(uid)) {
724                         fprintf(stderr, _("Failed to set uid %ld\n"),
725                                 (long)uid);
726                         exit(1);
727                 }
728         }
729
730         if (vpninfo->dtls_attempt_period && setup_dtls(vpninfo))
731                 fprintf(stderr, _("Set up DTLS failed; using SSL instead\n"));
732
733         vpn_progress(vpninfo, PRG_INFO,
734                      _("Connected %s as %s%s%s, using %s\n"), vpninfo->ifname,
735                      vpninfo->vpn_addr?:"",
736                      (vpninfo->vpn_addr6 && vpninfo->vpn_addr)?" + ":"",
737                      vpninfo->vpn_addr6?:"",
738                      (vpninfo->dtls_fd == -1) ?
739                      (vpninfo->deflate ? "SSL + deflate" : "SSL")
740                      : "DTLS");
741
742         if (!vpninfo->vpnc_script) {
743                 vpn_progress(vpninfo, PRG_INFO,
744                              _("No --script argument provided; DNS and routing are not configured\n"));
745                 vpn_progress(vpninfo, PRG_INFO,
746                              _("See http://www.infradead.org/openconnect/vpnc-script.html\n"));
747         }
748
749         if (background) {
750                 int pid;
751
752                 /* Open the pidfile before forking, so we can report errors
753                    more sanely. It's *possible* that we'll fail to write to
754                    it, but very unlikely. */
755                 if (pidfile != NULL) {
756                         fp = fopen(pidfile, "w");
757                         if (!fp) {
758                                 fprintf(stderr, _("Failed to open '%s' for write: %s\n"),
759                                         pidfile, strerror(errno));
760                                 exit(1);
761                         }
762                 }
763                 if ((pid = fork())) {
764                         if (fp) {
765                                 fprintf(fp, "%d\n", pid);
766                                 fclose(fp);
767                         }
768                         vpn_progress(vpninfo, PRG_INFO,
769                                      _("Continuing in background; pid %d\n"),
770                                      pid);
771                         exit(0);
772                 }
773                 if (fp)
774                         fclose(fp);
775         }
776         vpn_mainloop(vpninfo);
777         if (fp)
778                 unlink(pidfile);
779         exit(1);
780 }
781
782 static int write_new_config(void *_vpninfo, char *buf, int buflen)
783 {
784         struct openconnect_info *vpninfo = _vpninfo;
785         int config_fd;
786         int err;
787
788         config_fd = open(vpninfo->xmlconfig, O_WRONLY|O_TRUNC|O_CREAT, 0644);
789         if (config_fd < 0) {
790                 err = errno;
791                 fprintf(stderr, _("Failed to open %s for write: %s\n"),
792                         vpninfo->xmlconfig, strerror(err));
793                 return -err;
794         }
795
796         /* FIXME: We should actually write to a new tempfile, then rename */
797         if(write(config_fd, buf, buflen) != buflen) {
798                 err = errno;
799                 fprintf(stderr, _("Failed to write config to %s: %s\n"),
800                         vpninfo->xmlconfig, strerror(err));
801
802                 return -err;
803         }
804           
805         return 0;
806 }
807
808 void write_progress(void *_vpninfo, int level, const char *fmt, ...)
809 {
810         FILE *outf = level ? stdout : stderr;
811         va_list args;
812
813         if (verbose >= level) {
814                 va_start(args, fmt);
815                 vfprintf(outf, fmt, args);
816                 va_end(args);
817                 fflush(outf);
818         }
819 }
820
821 #ifdef ANDROID
822 void syslog_progress(void *_vpninfo, int level, const char *fmt, ...)
823 {
824         static int l[4] = {
825                 ANDROID_LOG_ERROR,      /* PRG_ERR   */
826                 ANDROID_LOG_INFO,       /* PRG_INFO  */
827                 ANDROID_LOG_DEBUG,      /* PRG_DEBUG */
828                 ANDROID_LOG_DEBUG       /* PRG_TRACE */
829         };
830         va_list args, args2;
831
832         if (verbose >= level) {
833                 va_start(args, fmt);
834                 va_copy(args2, args);
835                 __android_log_vprint(l[level], "openconnect", fmt, args);
836                 /* Android wants it to stderr too, so the GUI can scrape
837                    it and display it as well as going to syslog */
838                 vfprintf(stderr, fmt, args2);
839                 va_end(args);
840                 va_end(args2);
841         }
842 }
843 #else /* !ANDROID */
844 void syslog_progress(void *_vpninfo, int level, const char *fmt, ...)
845 {
846         int priority = level ? LOG_INFO : LOG_NOTICE;
847         va_list args;
848
849         if (verbose >= level) {
850                 va_start(args, fmt);
851                 vsyslog(priority, fmt, args);
852                 va_end(args);
853         }
854 }
855 #endif
856
857 struct accepted_cert {
858         struct accepted_cert *next;
859         char fingerprint[EVP_MAX_MD_SIZE * 2 + 1];
860         char host[0];
861 } *accepted_certs;
862
863 static int validate_peer_cert(void *_vpninfo, X509 *peer_cert,
864                               const char *reason)
865 {
866         struct openconnect_info *vpninfo = _vpninfo;
867         char fingerprint[EVP_MAX_MD_SIZE * 2 + 1];
868         struct accepted_cert *this;
869         int ret;
870
871         if (nocertcheck)
872                 return 0;
873
874         ret = openconnect_get_cert_sha1(vpninfo, peer_cert, fingerprint);
875         if (ret)
876                 return ret;
877
878         for (this = accepted_certs; this; this = this->next) {
879                 if (!strcasecmp(this->host, vpninfo->hostname) &&
880                     !strcasecmp(this->fingerprint, fingerprint))
881                         return 0;
882         }
883         
884         while (1) {
885                 UI *ui;
886                 char buf[6];
887                 int ret;
888
889                 fprintf(stderr,
890                         _("\nCertificate from VPN server \"%s\" failed verification.\n"
891                           "Reason: %s\n"), vpninfo->hostname, reason);
892                 if (non_inter)
893                         return -EINVAL;
894
895                 fflush(stderr);
896
897                 ui = UI_new();
898                 UI_add_input_string(ui, _("Enter 'yes' to accept, 'no' to abort; anything else to view: "),
899                                     UI_INPUT_FLAG_ECHO, buf, 2, 5);
900                 ret = UI_process(ui);
901                 UI_free(ui);
902                 if (ret == -2)
903                         return -EINVAL;
904                 if (ret == -1)
905                         buf[0] = 0;
906
907                 if (!strcasecmp(buf, _("yes"))) {
908                         struct accepted_cert *newcert = malloc(sizeof(*newcert) +
909                                                                strlen(vpninfo->hostname) + 1);
910                         if (newcert) {
911                                 newcert->next = accepted_certs;
912                                 accepted_certs = newcert;
913                                 strcpy(newcert->fingerprint, fingerprint);
914                                 strcpy(newcert->host, vpninfo->hostname);
915                         }
916                         return 0;
917                 }
918                 if (!strcasecmp(buf, _("no")))
919                         return -EINVAL;
920
921                 X509_print_fp(stderr, peer_cert);
922                 fprintf(stderr, _("SHA1 fingerprint: %s\n"), fingerprint);
923         }
924                                 
925 }