Warn when running Linux CSD trojan on non-Linux system
[platform/upstream/openconnect.git] / http.c
1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008 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 #define _GNU_SOURCE
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 #include <pwd.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <openssl/ssl.h>
38 #include <openssl/err.h>
39 #include <openssl/engine.h>
40
41 #include "openconnect.h"
42
43 #define MAX_BUF_LEN 131072
44 /*
45  * We didn't really want to have to do this for ourselves -- one might have
46  * thought that it would be available in a library somewhere. But neither
47  * cURL nor Neon have reliable cross-platform ways of either using a cert
48  * from the TPM, or just reading from / writing to a transport which is
49  * provided by their caller.
50  */
51
52 static int process_http_response(struct openconnect_info *vpninfo, int *result,
53                                  int (*header_cb)(struct openconnect_info *, char *, char *),
54                                  char *body, int buf_len)
55 {
56         char buf[MAX_BUF_LEN];
57         int bodylen = 0;
58         int done = 0;
59         int http10 = 0, closeconn = 0;
60         int i;
61
62         if (openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)) < 0) {
63                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTPS response\n");
64                 return -EINVAL;
65         }
66
67  cont:
68         if (!strncmp(buf, "HTTP/1.0 ", 9)) {
69                 http10 = 1;
70                 closeconn = 1;
71         }
72
73         if ((!http10 && strncmp(buf, "HTTP/1.1 ", 9)) || !(*result = atoi(buf+9))) {
74                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to parse HTTP response '%s'\n", buf);
75                 return -EINVAL;
76         }
77
78         vpninfo->progress(vpninfo, PRG_TRACE, "Got HTTP response: %s\n", buf);
79
80         /* Eat headers... */
81         while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
82                 char *colon;
83
84                 vpninfo->progress(vpninfo, PRG_TRACE, "%s\n", buf);
85
86                 if (i < 0) {
87                         vpninfo->progress(vpninfo, PRG_ERR, "Error processing HTTP response\n");
88                         return -EINVAL;
89                 }
90                 colon = strchr(buf, ':');
91                 if (!colon) {
92                         vpninfo->progress(vpninfo, PRG_ERR, "Ignoring unknown HTTP response line '%s'\n", buf);
93                         continue;
94                 }
95                 *(colon++) = 0;
96                 if (*colon == ' ')
97                         colon++;
98
99                 if (!strcmp(buf, "Connection") && !strcmp(colon, "Close"))
100                         closeconn = 1;
101
102                 if (!strcmp(buf, "Location")) {
103                         vpninfo->redirect_url = strdup(colon);
104                         if (!vpninfo->redirect_url)
105                                 return -ENOMEM;
106                 }
107                 if (!strcmp(buf, "Content-Length")) {
108                         bodylen = atoi(colon);
109                         if (bodylen < 0 || bodylen > buf_len) {
110                                 vpninfo->progress(vpninfo, PRG_ERR, "Response body too large for buffer (%d > %d)\n",
111                                         bodylen, buf_len);
112                                 return -EINVAL;
113                         }
114                 }
115                 if (!strcmp(buf, "Set-Cookie")) {
116                         struct vpn_option *new, **this;
117                         char *semicolon = strchr(colon, ';');
118                         char *equals = strchr(colon, '=');
119
120                         if (semicolon)
121                                 *semicolon = 0;
122
123                         if (!equals) {
124                                 vpninfo->progress(vpninfo, PRG_ERR, "Invalid cookie offered: %s\n", buf);
125                                 return -EINVAL;
126                         }
127                         *(equals++) = 0;
128
129                         if (*equals) {
130                                 new = malloc(sizeof(*new));
131                                 if (!new) {
132                                         vpninfo->progress(vpninfo, PRG_ERR, "No memory for allocating cookies\n");
133                                         return -ENOMEM;
134                                 }
135                                 new->next = NULL;
136                                 new->option = strdup(colon);
137                                 new->value = strdup(equals);
138                         } else {
139                                 /* Kill cookie; don't replace it */
140                                 new = NULL;
141                         }
142                         for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
143                                 if (!strcmp(colon, (*this)->option)) {
144                                         /* Replace existing cookie */
145                                         if (new)
146                                                 new->next = (*this)->next;
147                                         else
148                                                 new = (*this)->next;
149
150                                         free((*this)->option);
151                                         free((*this)->value);
152                                         free(*this);
153                                         *this = new;
154                                         break;
155                                 }
156                         }
157                         if (new && !*this) {
158                                 *this = new;
159                                 new->next = NULL;
160                         }
161                 }
162                 if (!strcmp(buf, "Transfer-Encoding")) {
163                         if (!strcmp(colon, "chunked"))
164                                 bodylen = -1;
165                         else {
166                                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown Transfer-Encoding: %s\n", colon);
167                                 return -EINVAL;
168                         }
169                 }
170                 if (header_cb && !strncmp(buf, "X-", 2))
171                         header_cb(vpninfo, buf, colon);
172         }
173
174         /* Handle 'HTTP/1.1 100 Continue'. Not that we should ever see it */
175         if (*result == 100)
176                 goto cont;
177
178         /* Now the body, if there is one */
179         if (!bodylen)
180                 goto fin;
181
182         if (http10) {
183                 /* HTTP 1.0 response. Just eat all we can. */
184                 while (1) {
185                         i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
186                         if (i < 0)
187                                 goto fin;
188                         done += i;
189                 }
190         }
191         /* If we were given Content-Length, it's nice and easy... */
192         if (bodylen > 0) {
193                 while (done < bodylen) {
194                         i = SSL_read(vpninfo->https_ssl, body + done, bodylen - done);
195                         if (i < 0) {
196                                 vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
197                                 return -EINVAL;
198                         }
199                         done += i;
200                 }
201                 goto fin;
202         }
203
204         /* ... else, chunked */
205         while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
206                 int chunklen, lastchunk = 0;
207
208                 if (i < 0) {
209                         vpninfo->progress(vpninfo, PRG_ERR, "Error fetching chunk header\n");
210                         exit(1);
211                 }
212                 chunklen = strtol(buf, NULL, 16);
213                 if (!chunklen) {
214                         lastchunk = 1;
215                         goto skip;
216                 }
217                 if (chunklen + done > buf_len) {
218                         vpninfo->progress(vpninfo, PRG_ERR, "Response body too large for buffer (%d > %d)\n",
219                                 chunklen + done, buf_len);
220                         return -EINVAL;
221                 }
222                 while (chunklen) {
223                         i = SSL_read(vpninfo->https_ssl, body + done, chunklen);
224                         if (i < 0) {
225                                 vpninfo->progress(vpninfo, PRG_ERR, "Error reading HTTP response body\n");
226                                 return -EINVAL;
227                         }
228                         chunklen -= i;
229                         done += i;
230                 }
231         skip:
232                 if ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
233                         if (i < 0) {
234                                 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTP response body\n");
235                         } else {
236                                 vpninfo->progress(vpninfo, PRG_ERR, "Error in chunked decoding. Expected '', got: '%s'",
237                                         buf);
238                         }
239                         return -EINVAL;
240                 }
241
242                 if (lastchunk)
243                         break;
244         }
245  fin:
246         if (closeconn) {
247                 SSL_free(vpninfo->https_ssl);
248                 vpninfo->https_ssl = NULL;
249                 close(vpninfo->ssl_fd);
250                 vpninfo->ssl_fd = -1;
251         }
252         body[done] = 0;
253         return done;
254 }
255
256 static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
257                         char *server_sha1)
258 {
259         struct vpn_option *opt;
260         char buf[MAX_BUF_LEN];
261         int result, buflen;
262         unsigned char local_sha1_bin[SHA_DIGEST_LENGTH];
263         char local_sha1_ascii[(SHA_DIGEST_LENGTH * 2)+1];
264         EVP_MD_CTX c;
265         int i;
266
267         sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
268         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
269         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
270         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
271         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
272
273         if (vpninfo->cookies) {
274                 sprintf(buf + strlen(buf),  "Cookie: ");
275                 for (opt = vpninfo->cookies; opt; opt = opt->next)
276                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
277                                       opt->value, opt->next ? "; " : "\r\n");
278         }
279         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
280
281         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
282
283         buflen = process_http_response(vpninfo, &result, NULL, buf, MAX_BUF_LEN);
284         if (buflen < 0) {
285                 /* We'll already have complained about whatever offended us */
286                 return -EINVAL;
287         }
288
289         if (result != 200)
290                 return -EINVAL;
291
292
293         EVP_MD_CTX_init(&c);
294         EVP_Digest(buf, buflen, local_sha1_bin, NULL, EVP_sha1(), NULL);
295         EVP_MD_CTX_cleanup(&c);
296
297         for (i = 0; i < SHA_DIGEST_LENGTH; i++)
298                 sprintf(&local_sha1_ascii[i*2], "%02x", local_sha1_bin[i]);
299
300         if (strcasecmp(server_sha1, local_sha1_ascii)) {
301                 vpninfo->progress(vpninfo, PRG_ERR, "Downloaded config file did not match intended SHA1\n");
302                 return -EINVAL;
303         }
304
305         return vpninfo->write_new_config(vpninfo, buf, buflen);
306 }
307
308 static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int buflen)
309 {
310         char fname[16];
311         int fd;
312
313         if (!vpninfo->uid_csd_given) {
314                 vpninfo->progress(vpninfo, PRG_ERR, "Error: You are trying to "
315                                   "run insecure CSD code without specifying the CSD user.\n"
316                                   "       Use command line option \"--csd-user\"\n");
317                 exit(1);
318         }
319
320 #ifndef __linux__
321         vpninfo->progress(vpninfo, PRG_INFO,
322                           "Trying to run Linux CSD trojan script.");
323 #endif
324
325         sprintf(fname, "/tmp/csdXXXXXX");
326         fd = mkstemp(fname);
327         if (fd < 0) {
328                 int err = -errno;
329                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open temporary CSD script file: %s\n",
330                                   strerror(errno));
331                 return err;
332         }
333         write(fd, buf, buflen);
334         fchmod(fd, 0755);
335         close(fd);
336
337         if (!fork()) {
338                 X509 *cert = SSL_get_peer_certificate(vpninfo->https_ssl);
339                 char certbuf[EVP_MAX_MD_SIZE * 2 + 1];
340                 char *csd_argv[32];
341                 int i = 0;
342
343                 if (vpninfo->uid_csd != getuid()) {
344                         struct passwd *pw;
345
346                         if (setuid(vpninfo->uid_csd)) {
347                                 fprintf(stderr, "Failed to set uid %d\n",
348                                         vpninfo->uid_csd);
349                                 exit(1);
350                         }
351                         if (!(pw = getpwuid(vpninfo->uid_csd))) {
352                                 fprintf(stderr, "Invalid user uid=%d\n",
353                                         vpninfo->uid_csd);
354                                 exit(1);
355                         }
356                         setenv("HOME", pw->pw_dir, 1);
357                         chdir(pw->pw_dir);
358                 }
359                 if (vpninfo->uid_csd == 0) {
360                         fprintf(stderr, "Warning: you are running insecure "
361                                 "CSD code with root privileges\n"
362                                 "\t Use command line option \"--csd-user\"\n");
363                 }
364
365                 csd_argv[i++] = fname;
366                 csd_argv[i++] = "-ticket";
367                 asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket);
368                 csd_argv[i++] = "-stub";
369                 csd_argv[i++] = "\"0\"";
370                 csd_argv[i++] = "-group";
371                 asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"");
372                 get_cert_md5_fingerprint(vpninfo, cert, certbuf);
373                 csd_argv[i++] = "-certhash";
374                 asprintf(&csd_argv[i++], "\"%s:%s\"", certbuf, vpninfo->cert_md5_fingerprint ?: "");
375                 csd_argv[i++] = "-url";
376                 asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl);
377                 /* WTF would it want to know this for? */
378                 csd_argv[i++] = "-vpnclient";
379                 csd_argv[i++] = "\"/opt/cisco/vpn/bin/vpnui";
380                 csd_argv[i++] = "-connect";
381                 asprintf(&csd_argv[i++], "https://%s/%s", vpninfo->hostname, vpninfo->csd_preurl);
382                 csd_argv[i++] = "-connectparam";
383                 asprintf(&csd_argv[i++], "#csdtoken=%s\"", vpninfo->csd_token);
384                 csd_argv[i++] = "-langselen";
385                 csd_argv[i++] = NULL;
386
387                 execv(fname, csd_argv);
388                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to exec CSD script %s\n", fname);
389                 exit(1);
390         }
391
392         free(vpninfo->csd_stuburl);
393         vpninfo->csd_stuburl = NULL;
394         vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
395                                   (vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
396         vpninfo->csd_waiturl = NULL;
397         vpninfo->csd_scriptname = strdup(fname);
398
399         /* AB: add cookie "sdesktop=__TOKEN__" */
400         {
401                 struct vpn_option *new, **this;
402                 new = malloc(sizeof(*new));
403                 if (!new) {
404                         vpninfo->progress(vpninfo, PRG_ERR, "No memory for allocating cookies\n");
405                         return -ENOMEM;
406                 }
407                 new->next = NULL;
408                 new->option = strdup("sdesktop");
409                 new->value = strdup(vpninfo->csd_token);
410
411                 for (this = &vpninfo->cookies; *this; this = &(*this)->next) {
412                         if (!strcmp(new->option, (*this)->option)) {
413                                 /* Replace existing cookie */
414                                 if (new)
415                                         new->next = (*this)->next;
416                                 else
417                                         new = (*this)->next;
418
419                                 free((*this)->option);
420                                 free((*this)->value);
421                                 free(*this);
422                                 *this = new;
423                                 break;
424                         }
425                 }
426                 if (new && !*this) {
427                         *this = new;
428                         new->next = NULL;
429                 }
430         }
431         return 0;
432 }
433
434 #ifdef __sun__
435 char *local_strcasestr(const char *haystack, const char *needle)
436 {
437         int hlen = strlen(haystack);
438         int nlen = strlen(needle);
439         int i, j;
440
441         for (i = 0; i < hlen - nlen + 1; i++) {
442                 for (j = 0; j < nlen; j++) {
443                         if (tolower(haystack[i + j]) != 
444                             tolower(needle[j]))
445                                 break;
446                 }
447                 if (j == nlen)
448                         return (char *)haystack + i;
449         }
450         return NULL;
451 }
452 #define strcasestr local_strcasestr
453 #endif
454
455 /* Return value:
456  *  < 0, on error
457  *  = 0, no cookie (user cancel)
458  *  = 1, obtained cookie
459  */
460 int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
461 {
462         struct vpn_option *opt, *next;
463         char buf[MAX_BUF_LEN];
464         int result, buflen;
465         char request_body[2048];
466         char *request_body_type = NULL;
467         char *method = "GET";
468
469  retry:
470         if (!vpninfo->https_ssl && openconnect_open_https(vpninfo)) {
471                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to open HTTPS connection to %s\n",
472                         vpninfo->hostname);
473                 return -EINVAL;
474         }
475
476         /*
477          * It would be nice to use cURL for this, but we really need to guarantee
478          * that we'll be using OpenSSL (for the TPM stuff), and it doesn't seem
479          * to have any way to let us provide our own socket read/write functions.
480          * We can only provide a socket _open_ function. Which would require having
481          * a socketpair() and servicing the "other" end of it.
482          *
483          * So we process the HTTP for ourselves...
484          */
485         sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
486         sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
487         sprintf(buf + strlen(buf),  "User-Agent: %s\r\n", vpninfo->useragent);
488         sprintf(buf + strlen(buf),  "Accept: */*\r\n");
489         sprintf(buf + strlen(buf),  "Accept-Encoding: identity\r\n");
490
491         if (vpninfo->cookies) {
492                 sprintf(buf + strlen(buf),  "Cookie: ");
493                 for (opt = vpninfo->cookies; opt; opt = opt->next)
494                         sprintf(buf + strlen(buf),  "%s=%s%s", opt->option,
495                                       opt->value, opt->next ? "; " : "\r\n");
496         }
497         if (request_body_type) {
498                 sprintf(buf + strlen(buf),  "Content-Type: %s\r\n",
499                               request_body_type);
500                 sprintf(buf + strlen(buf),  "Content-Length: %zd\r\n",
501                               strlen(request_body));
502         }
503         sprintf(buf + strlen(buf),  "X-Transcend-Version: 1\r\n\r\n");
504         if (request_body_type)
505                 sprintf(buf + strlen(buf), "%s", request_body);
506
507         vpninfo->progress(vpninfo, PRG_INFO, "%s %s/%s\n", method,
508                           vpninfo->hostname, vpninfo->urlpath ?: "");
509
510         SSL_write(vpninfo->https_ssl, buf, strlen(buf));
511
512         buflen = process_http_response(vpninfo, &result, NULL, buf, MAX_BUF_LEN);
513         if (buflen < 0) {
514                 /* We'll already have complained about whatever offended us */
515                 exit(1);
516         }
517
518         if (result != 200 && vpninfo->redirect_url) {
519         redirect:
520                 if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
521                         /* New host. Tear down the existing connection and make a new one */
522                         char *host = vpninfo->redirect_url + 8;
523                         char *path = strchr(host, '/');
524
525                         free(vpninfo->urlpath);
526                         if (path) {
527                                 *(path++) = 0;
528                                 vpninfo->urlpath = strdup(path);
529                         } else
530                                 vpninfo->urlpath = NULL;
531
532                         if (strcmp(vpninfo->hostname, host)) {
533                                 free(vpninfo->hostname);
534                                 vpninfo->hostname = strdup(host);
535
536                                 /* Kill the existing connection, and a new one will happen */
537                                 SSL_free(vpninfo->https_ssl);
538                                 vpninfo->https_ssl = NULL;
539                                 close(vpninfo->ssl_fd);
540                                 vpninfo->ssl_fd = -1;
541
542                                 for (opt = vpninfo->cookies; opt; opt = next) {
543                                         next = opt->next;
544
545                                         free(opt->option);
546                                         free(opt->value);
547                                         free(opt);
548                                 }
549                                 vpninfo->cookies = NULL;
550                         }
551                         free(vpninfo->redirect_url);
552                         vpninfo->redirect_url = NULL;
553
554                         goto retry;
555                 } else if (vpninfo->redirect_url[0] == '/') {
556                         /* Absolute redirect within same host */
557                         free(vpninfo->urlpath);
558                         vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
559                         free(vpninfo->redirect_url);
560                         vpninfo->redirect_url = NULL;
561                         goto retry;
562                 } else {
563                         vpninfo->progress(vpninfo, PRG_ERR, "Relative redirect (to '%s') not supported\n",
564                                 vpninfo->redirect_url);
565                         return -EINVAL;
566                 }
567         }
568
569         if (vpninfo->csd_stuburl) {
570                 /* This is the CSD stub script, which we now need to run */
571                 result = run_csd_script(vpninfo, buf, buflen);
572                 if (result)
573                         return result;
574
575                 /* Now we'll be redirected to the waiturl */
576                 goto retry;
577         }
578         if (strncmp(buf, "<?xml", 5)) {
579                 /* Not XML? Perhaps it's HTML with a refresh... */
580                 if (strcasestr(buf, "http-equiv=\"refresh\"")) {
581                         vpninfo->progress(vpninfo, PRG_INFO, "Refreshing %s after 1 second...\n",
582                                           vpninfo->urlpath);
583                         sleep(1);
584                         goto retry;
585                 }
586                 vpninfo->progress(vpninfo, PRG_ERR, "Unknown response from server\n");
587                 return -EINVAL;
588         }
589         request_body[0] = 0;
590         result = parse_xml_response(vpninfo, buf, request_body, sizeof(request_body),
591                                     &method, &request_body_type);
592         if (!result)
593                 goto redirect;
594
595         if (result != 2)
596                 return result;
597         /* A return value of 2 means the XML form indicated
598            success. We _should_ have a cookie... */
599
600         for (opt = vpninfo->cookies; opt; opt = opt->next) {
601
602                 if (!strcmp(opt->option, "webvpn"))
603                         vpninfo->cookie = opt->value;
604                 else if (vpninfo->write_new_config && !strcmp(opt->option, "webvpnc")) {
605                         char *tok = opt->value;
606                         char *bu = NULL, *fu = NULL, *sha = NULL;
607
608                         do {
609                                 if (tok != opt->value)
610                                         *(tok++) = 0;
611
612                                 if (!strncmp(tok, "bu:", 3))
613                                         bu = tok + 3;
614                                 else if (!strncmp(tok, "fu:", 3))
615                                         fu = tok + 3;
616                                 else if (!strncmp(tok, "fh:", 3)) {
617                                         if (!strncasecmp(tok+3, vpninfo->xmlsha1,
618                                                          SHA_DIGEST_LENGTH * 2))
619                                                 break;
620                                         sha = tok + 3;
621                                 }
622                         } while ((tok = strchr(tok, '&')));
623
624                         if (bu && fu && sha)
625                                 fetch_config(vpninfo, bu, fu, sha);
626                 }
627         }
628         if (vpninfo->csd_scriptname) {
629                 unlink(vpninfo->csd_scriptname);
630                 free(vpninfo->csd_scriptname);
631                 vpninfo->csd_scriptname = NULL;
632         }
633         return 0;
634 }
635
636 char *openconnect_create_useragent(char *base)
637 {
638         char *uagent = malloc(strlen(base) + 1 + strlen(openconnect_version));
639         sprintf(uagent, "%s %s", base, openconnect_version);
640         return uagent;
641 }