K&R style function removal. Update functions to ANSI style.
[dragonfly.git] / usr.bin / ftp / fetch.c
1 /*      $NetBSD: fetch.c,v 1.16.2.1 1997/11/18 01:00:22 mellon Exp $    */
2
3 /*-
4  * Copyright (c) 1997 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason Thorpe and Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  *
38  * $NetBSD: fetch.c,v 1.16.2.1 1997/11/18 01:00:22 mellon Exp $
39  * $FreeBSD: src/usr.bin/ftp/fetch.c,v 1.12.2.6 2002/10/19 12:50:26 roam Exp $
40  * $DragonFly: src/usr.bin/ftp/Attic/fetch.c,v 1.3 2003/10/04 20:36:44 hmp Exp $
41  */
42
43 #include <sys/cdefs.h>
44
45 /*
46  * FTP User Program -- Command line file retrieval
47  */
48
49 #include <sys/types.h>
50 #include <sys/param.h>
51 #include <sys/socket.h>
52
53 #include <netinet/in.h>
54
55 #include <arpa/ftp.h>
56 #include <arpa/inet.h>
57
58 #include <ctype.h>
59 #include <err.h>
60 #include <errno.h>
61 #include <netdb.h>
62 #include <fcntl.h>
63 #include <signal.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <unistd.h>
68
69 #include "ftp_var.h"
70
71 static int      url_get(const char *, const char *);
72 void            aborthttp(int);
73
74
75 #define FTP_URL         "ftp://"        /* ftp URL prefix */
76 #define HTTP_URL        "http://"       /* http URL prefix */
77 #define FTP_PROXY       "ftp_proxy"     /* env var with ftp proxy location */
78 #define HTTP_PROXY      "http_proxy"    /* env var with http proxy location */
79
80
81 #define EMPTYSTRING(x)  ((x) == NULL || (*(x) == '\0'))
82
83 jmp_buf httpabort;
84
85 /*
86  * Retrieve URL, via the proxy in $proxyvar if necessary.
87  * Modifies the string argument given.
88  * Returns -1 on failure, 0 on success
89  */
90 static int
91 url_get(const char *origline, const char *proxyenv)
92 {
93         struct addrinfo hints;
94         struct addrinfo *res0, *res;
95         char nameinfo[2 * INET6_ADDRSTRLEN + 1];
96         int i, out, isftpurl;
97         char *port;
98         volatile int s;
99         ssize_t len;
100         char c, *cp, *ep, *http_buffer, *portnum, *path, buf[4096];
101         const char *savefile;
102         char *line, *proxy, *host;
103         volatile sig_t oldintr;
104         off_t hashbytes;
105         int error;
106
107         s = -1;
108         proxy = NULL;
109         isftpurl = 0;
110         res0 = NULL;
111
112 #ifdef __GNUC__                 /* XXX: to shut up gcc warnings */
113         (void)&savefile;
114         (void)&proxy;
115         (void)&res0;
116 #endif
117
118         line = strdup(origline);
119         if (line == NULL)
120                 errx(1, "Can't allocate memory to parse URL");
121         if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
122                 host = line + sizeof(HTTP_URL) - 1;
123         else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
124                 host = line + sizeof(FTP_URL) - 1;
125                 isftpurl = 1;
126         } else
127                 errx(1, "url_get: Invalid URL '%s'", line);
128
129         path = strchr(host, '/');               /* find path */
130         if (EMPTYSTRING(path)) {
131                 if (isftpurl)
132                         goto noftpautologin;
133                 warnx("Invalid URL (no `/' after host): %s", origline);
134                 goto cleanup_url_get;
135         }
136         *path++ = '\0';
137         if (EMPTYSTRING(path)) {
138                 if (isftpurl)
139                         goto noftpautologin;
140                 warnx("Invalid URL (no file after host): %s", origline);
141                 goto cleanup_url_get;
142         }
143
144         savefile = strrchr(path, '/');                  /* find savefile */
145         if (savefile != NULL)
146                 savefile++;
147         else
148                 savefile = path;
149         if (EMPTYSTRING(savefile)) {
150                 if (isftpurl)
151                         goto noftpautologin;
152                 warnx("Invalid URL (no file after directory): %s", origline);
153                 goto cleanup_url_get;
154         }
155
156         if (proxyenv != NULL) {                         /* use proxy */
157                 proxy = strdup(proxyenv);
158                 if (proxy == NULL)
159                         errx(1, "Can't allocate memory for proxy URL.");
160                 if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
161                         host = proxy + sizeof(HTTP_URL) - 1;
162                 else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
163                         host = proxy + sizeof(FTP_URL) - 1;
164                 else {
165                         warnx("Malformed proxy URL: %s", proxyenv);
166                         goto cleanup_url_get;
167                 }
168                 if (EMPTYSTRING(host)) {
169                         warnx("Malformed proxy URL: %s", proxyenv);
170                         goto cleanup_url_get;
171                 }
172                 *--path = '/';                  /* add / back to real path */
173                 path = strchr(host, '/');       /* remove trailing / on host */
174                 if (! EMPTYSTRING(path))
175                         *path++ = '\0';
176                 path = line;
177         }
178
179         if (*host == '[' && (portnum = strrchr(host, ']'))) {   /* IPv6 URL */
180                 *portnum++ = '\0';
181                 host++;
182                 if (*portnum == ':')
183                         portnum++;
184                 else
185                         portnum = NULL;
186         } else {
187                 portnum = strrchr(host, ':');   /* find portnum */
188                 if (portnum != NULL)
189                         *portnum++ = '\0';
190         }
191         
192         if (debug)
193                 printf("host %s, port %s, path %s, save as %s.\n",
194                     host, portnum, path, savefile);
195
196         if (! EMPTYSTRING(portnum)) {
197                 port = portnum;
198         } else
199                 port = httpport;
200
201         memset(&hints, 0, sizeof(hints));
202         hints.ai_family = family;
203         hints.ai_socktype = SOCK_STREAM;
204         error = getaddrinfo(host, port, &hints, &res);
205         res0 = res;
206         if (error) {
207                 warnx("%s: %s", host, gai_strerror(error));
208                 if (error == EAI_SYSTEM)
209                         warnx("%s: %s", host, strerror(errno));
210                 goto cleanup_url_get;
211         }
212
213         while (1)
214       {
215         ai_unmapped(res);
216         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
217         if (s == -1) {
218                 res = res->ai_next;
219                 if (res)
220                         continue;
221                 warn("Can't create socket");
222                 goto cleanup_url_get;
223         }
224
225         if (dobind) {
226                 struct addrinfo *bindres;
227                 int binderr = -1;
228
229                 for (bindres = bindres0;
230                      bindres != NULL;
231                      bindres = bindres->ai_next)
232                         if (bindres->ai_family == res->ai_family)
233                                 break;
234                 if (bindres == NULL)
235                         bindres = bindres0;
236                 binderr = bind(s, bindres->ai_addr, bindres->ai_addrlen);
237                 if (binderr == -1)
238               {
239                 res = res->ai_next;
240                 if (res) {
241                         (void)close(s);
242                         continue;
243                 }
244                 getnameinfo(bindres->ai_addr, bindres->ai_addrlen,
245                             nameinfo, sizeof(nameinfo), NULL, 0,
246                             NI_NUMERICHOST);
247                 /* XXX check error? */
248                 warn("Can't bind to %s", nameinfo);
249                 goto cleanup_url_get;
250               }
251         }
252
253         if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
254                 res = res->ai_next;
255                 if (res) {
256                         (void)close(s);
257                         continue;
258                 }
259                 warn("Can't connect to %s", host);
260                 goto cleanup_url_get;
261         }
262
263         break;
264       }
265         freeaddrinfo(res0);
266         res0 = NULL;
267
268         /*
269          * Construct and send the request.  We're expecting a return
270          * status of "200". Proxy requests don't want leading /.
271          */
272         if (!proxy) {
273                 printf("Requesting %s\n", origline);
274                 len = asprintf(&http_buffer,
275                     "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host);
276         } else {
277                 printf("Requesting %s (via %s)\n", origline, proxyenv);
278                 len = asprintf(&http_buffer,
279                     "GET %s HTTP/1.0\r\n\r\n", path);
280         }
281         if (len < 0 || http_buffer == NULL) {
282                 warnx("Failed to format HTTP request");
283                 goto cleanup_url_get;
284         }
285         if (write(s, http_buffer, len) < len) {
286                 warn("Writing HTTP request");
287                 free(http_buffer);
288                 goto cleanup_url_get;
289         }
290         free(http_buffer);
291         memset(buf, 0, sizeof(buf));
292         for (cp = buf; cp < buf + sizeof(buf); ) {
293                 if (read(s, cp, 1) != 1)
294                         goto improper;
295                 if (*cp == '\r')
296                         continue;
297                 if (*cp == '\n')
298                         break;
299                 cp++;
300         }
301         buf[sizeof(buf) - 1] = '\0';            /* sanity */
302         cp = strchr(buf, ' ');
303         if (cp == NULL)
304                 goto improper;
305         else
306                 cp++;
307         if (strncmp(cp, "200", 3)) {
308                 warnx("Error retrieving file: %s", cp);
309                 goto cleanup_url_get;
310         }
311
312         /*
313          * Read the rest of the header.
314          */
315         memset(buf, 0, sizeof(buf));
316         c = '\0';
317         for (cp = buf; cp < buf + sizeof(buf); ) {
318                 if (read(s, cp, 1) != 1)
319                         goto improper;
320                 if (*cp == '\r')
321                         continue;
322                 if (*cp == '\n' && c == '\n')
323                         break;
324                 c = *cp;
325                 cp++;
326         }
327         buf[sizeof(buf) - 1] = '\0';            /* sanity */
328
329         /*
330          * Look for the "Content-length: " header.
331          */
332 #define CONTENTLEN "Content-Length: "
333         for (cp = buf; *cp != '\0'; cp++) {
334                 if (tolower((unsigned char)*cp) == 'c' &&
335                     strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
336                         break;
337         }
338         if (*cp != '\0') {
339                 cp += sizeof(CONTENTLEN) - 1;
340                 ep = strchr(cp, '\n');
341                 if (ep == NULL)
342                         goto improper;
343                 else
344                         *ep = '\0';
345                 filesize = strtol(cp, &ep, 10);
346                 if (filesize < 1 || *ep != '\0')
347                         goto improper;
348         } else
349                 filesize = -1;
350
351         /* Open the output file. */
352         out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
353         if (out < 0) {
354                 warn("Can't open %s", savefile);
355                 goto cleanup_url_get;
356         }
357
358         /* Trap signals */
359         oldintr = NULL;
360         if (setjmp(httpabort)) {
361                 if (oldintr)
362                         (void)signal(SIGINT, oldintr);
363                 goto cleanup_url_get;
364         }
365         oldintr = signal(SIGINT, aborthttp);
366
367         bytes = 0;
368         hashbytes = mark;
369         progressmeter(-1);
370
371         /* Finally, suck down the file. */
372         i = 0;
373         while ((len = read(s, buf, sizeof(buf))) > 0) {
374                 bytes += len;
375                 for (cp = buf; len > 0; len -= i, cp += i) {
376                         if ((i = write(out, cp, len)) == -1) {
377                                 warn("Writing %s", savefile);
378                                 goto cleanup_url_get;
379                         }
380                         else if (i == 0)
381                                 break;
382                 }
383                 if (hash && !progress) {
384                         while (bytes >= hashbytes) {
385                                 (void)putchar('#');
386                                 hashbytes += mark;
387                         }
388                         (void)fflush(stdout);
389                 }
390         }
391         if (hash && !progress && bytes > 0) {
392                 if (bytes < mark)
393                         (void)putchar('#');
394                 (void)putchar('\n');
395                 (void)fflush(stdout);
396         }
397         if (len != 0) {
398                 warn("Reading from socket");
399                 goto cleanup_url_get;
400         }
401         progressmeter(1);
402         if (verbose)
403                 puts("Successfully retrieved file.");
404         (void)signal(SIGINT, oldintr);
405
406         close(s);
407         close(out);
408         if (proxy)
409                 free(proxy);
410         free(line);
411         return (0);
412
413 noftpautologin:
414         warnx(
415             "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
416         goto cleanup_url_get;
417
418 improper:
419         warnx("Improper response from %s", host);
420
421 cleanup_url_get:
422         if (s != -1)
423                 close(s);
424         if (proxy)
425                 free(proxy);
426         free(line);
427         if (res0 != NULL)
428                 freeaddrinfo(res0);
429         return (-1);
430 }
431
432 /*
433  * Abort a http retrieval
434  */
435 void
436 aborthttp(int notused)
437 {
438
439         alarmtimer(0);
440         puts("\nhttp fetch aborted.");
441         (void)fflush(stdout);
442         longjmp(httpabort, 1);
443 }
444
445 /*
446  * Retrieve multiple files from the command line, transferring
447  * files of the form "host:path", "ftp://host/path" using the
448  * ftp protocol, and files of the form "http://host/path" using
449  * the http protocol.
450  * If path has a trailing "/", then return (-1);
451  * the path will be cd-ed into and the connection remains open,
452  * and the function will return -1 (to indicate the connection
453  * is alive).
454  * If an error occurs the return value will be the offset+1 in
455  * argv[] of the file that caused a problem (i.e, argv[x]
456  * returns x+1)
457  * Otherwise, 0 is returned if all files retrieved successfully.
458  */
459 int
460 auto_fetch(int argc, char **argv)
461 {
462         static char lasthost[MAXHOSTNAMELEN];
463         char *xargv[5];
464         char *cp, *line, *host, *dir, *file, *portnum;
465         char *user, *pass;
466         char *ftpproxy, *httpproxy;
467         int rval, xargc;
468         volatile int argpos;
469         int dirhasglob, filehasglob;
470         char rempath[MAXPATHLEN];
471
472         argpos = 0;
473
474         if (setjmp(toplevel)) {
475                 if (connected)
476                         disconnect(0, NULL);
477                 return (argpos + 1);
478         }
479         (void)signal(SIGINT, (sig_t)intr);
480         (void)signal(SIGPIPE, (sig_t)lostpeer);
481
482         ftpproxy = getenv(FTP_PROXY);
483         httpproxy = getenv(HTTP_PROXY);
484
485         /*
486          * Loop through as long as there's files to fetch.
487          */
488         for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
489                 if (strchr(argv[argpos], ':') == NULL)
490                         break;
491                 host = dir = file = portnum = user = pass = NULL;
492
493                 /*
494                  * We muck with the string, so we make a copy.
495                  */
496                 line = strdup(argv[argpos]);
497                 if (line == NULL)
498                         errx(1, "Can't allocate memory for auto-fetch.");
499
500                 /*
501                  * Try HTTP URL-style arguments first.
502                  */
503                 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
504                         if (url_get(line, httpproxy) == -1)
505                                 rval = argpos + 1;
506                         continue;
507                 }
508
509                 /*
510                  * Try FTP URL-style arguments next. If ftpproxy is
511                  * set, use url_get() instead of standard ftp.
512                  * Finally, try host:file.
513                  */
514                 host = line;
515                 if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
516                         if (ftpproxy) {
517                                 if (url_get(line, ftpproxy) == -1)
518                                         rval = argpos + 1;
519                                 continue;
520                         }
521                         host += sizeof(FTP_URL) - 1;
522                         dir = strchr(host, '/');
523
524                                 /* look for [user:pass@]host[:port] */
525                         pass = strpbrk(host, ":@/");
526                         if (pass == NULL || *pass == '/') {
527                                 pass = NULL;
528                                 goto parsed_url;
529                         }
530                         if (pass == host || *pass == '@') {
531 bad_ftp_url:
532                                 warnx("Invalid URL: %s", argv[argpos]);
533                                 rval = argpos + 1;
534                                 continue;
535                         }
536                         *pass++ = '\0';
537                         cp = strpbrk(pass, ":@/");
538                         if (cp == NULL || *cp == '/') {
539                                 portnum = pass;
540                                 pass = NULL;
541                                 goto parsed_url;
542                         }
543                         if (EMPTYSTRING(cp) || *cp == ':')
544                                 goto bad_ftp_url;
545                         *cp++ = '\0';
546                         user = host;
547                         if (EMPTYSTRING(user))
548                                 goto bad_ftp_url;
549                         host = cp;
550                         portnum = strchr(host, ':');
551                         if (portnum != NULL)
552                                 *portnum++ = '\0';
553                 } else {                        /* classic style `host:file' */
554                         char *end_brace;
555                         
556                         if (*host == '[' &&
557                             (end_brace = strrchr(host, ']')) != NULL) {
558                                 /*IPv6 addr in []*/
559                                 host++;
560                                 *end_brace = '\0';
561                                 dir = strchr(end_brace + 1, ':');
562                         } else
563                                 dir = strchr(host, ':');
564                 }
565 parsed_url:
566                 if (EMPTYSTRING(host)) {
567                         rval = argpos + 1;
568                         continue;
569                 }
570
571                 /*
572                  * If dir is NULL, the file wasn't specified
573                  * (URL looked something like ftp://host)
574                  */
575                 if (dir != NULL)
576                         *dir++ = '\0';
577
578                 /*
579                  * Extract the file and (if present) directory name.
580                  */
581                 if (! EMPTYSTRING(dir)) {
582                         cp = strrchr(dir, '/');
583                         if (cp != NULL) {
584                                 *cp++ = '\0';
585                                 file = cp;
586                         } else {
587                                 file = dir;
588                                 dir = NULL;
589                         }
590                 }
591                 if (debug)
592                         printf("user %s:%s host %s port %s dir %s file %s\n",
593                             user, pass, host, portnum, dir, file);
594
595                 /*
596                  * Set up the connection if we don't have one.
597                  */
598                 if (strcmp(host, lasthost) != 0) {
599                         int oautologin;
600
601                         (void)strcpy(lasthost, host);
602                         if (connected)
603                                 disconnect(0, NULL);
604                         xargv[0] = __progname;
605                         xargv[1] = host;
606                         xargv[2] = NULL;
607                         xargc = 2;
608                         if (! EMPTYSTRING(portnum)) {
609                                 xargv[2] = portnum;
610                                 xargv[3] = NULL;
611                                 xargc = 3;
612                         }
613                         oautologin = autologin;
614                         if (user != NULL)
615                                 autologin = 0;
616                         setpeer(xargc, xargv);
617                         autologin = oautologin;
618                         if ((connected == 0)
619                          || ((connected == 1) && !login(host, user, pass)) ) {
620                                 warnx("Can't connect or login to host `%s'",
621                                     host);
622                                 rval = argpos + 1;
623                                 continue;
624                         }
625
626                         /* Always use binary transfers. */
627                         setbinary(0, NULL);
628                 }
629                         /* cd back to '/' */
630                 xargv[0] = "cd";
631                 xargv[1] = "/";
632                 xargv[2] = NULL;
633                 cd(2, xargv);
634                 if (! dirchange) {
635                         rval = argpos + 1;
636                         continue;
637                 }
638
639                 dirhasglob = filehasglob = 0;
640                 if (doglob) {
641                         if (! EMPTYSTRING(dir) &&
642                             strpbrk(dir, "*?[]{}") != NULL)
643                                 dirhasglob = 1;
644                         if (! EMPTYSTRING(file) &&
645                             strpbrk(file, "*?[]{}") != NULL)
646                                 filehasglob = 1;
647                 }
648
649                 /* Change directories, if necessary. */
650                 if (! EMPTYSTRING(dir) && !dirhasglob) {
651                         xargv[0] = "cd";
652                         xargv[1] = dir;
653                         xargv[2] = NULL;
654                         cd(2, xargv);
655                         if (! dirchange) {
656                                 rval = argpos + 1;
657                                 continue;
658                         }
659                 }
660
661                 if (EMPTYSTRING(file)) {
662                         rval = -1;
663                         continue;
664                 }
665
666                 if (!verbose)
667                         printf("Retrieving %s/%s\n", dir ? dir : "", file);
668
669                 if (dirhasglob) {
670                         snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
671                         file = rempath;
672                 }
673
674                 /* Fetch the file(s). */
675                 xargv[0] = "get";
676                 xargv[1] = file;
677                 xargv[2] = NULL;
678                 if (dirhasglob || filehasglob) {
679                         int ointeractive;
680
681                         ointeractive = interactive;
682                         interactive = 0;
683                         xargv[0] = "mget";
684                         mget(2, xargv);
685                         interactive = ointeractive;
686                 } else
687                         get(2, xargv);
688
689                 if ((code / 100) != COMPLETE)
690                         rval = argpos + 1;
691         }
692         if (connected && rval != -1)
693                 disconnect(0, NULL);
694         return (rval);
695 }
696
697 int
698 isurl(const char *p)
699 {
700         char *path, pton_buf[16];
701
702         if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0
703          || strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
704                 return 1;
705         }
706         if (*p == '[' && (path = strrchr(p, ']')) != NULL) /*IPv6 addr in []*/
707                 return (*(++path) == ':') ? 1 : 0;
708 #ifdef INET6
709         if (inet_pton(AF_INET6, p, pton_buf) == 1) /* raw IPv6 addr */
710                 return 0;
711 #endif
712         if (strchr(p, ':') != NULL) /* else, if ':' exist */
713                 return 1;
714         return 0;
715 }