]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/ftp/fetch.c
This commit was generated by cvs2svn to compensate for changes in r44743,
[FreeBSD/FreeBSD.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
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __RCSID("$Id: fetch.c,v 1.4 1997/12/16 08:22:35 ache Exp $");
42 __RCSID_SOURCE("$NetBSD: fetch.c,v 1.16.2.1 1997/11/18 01:00:22 mellon Exp $");
43 #endif /* not lint */
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 <netdb.h>
61 #include <fcntl.h>
62 #include <signal.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <unistd.h>
67
68 #include "ftp_var.h"
69
70 static int      url_get __P((const char *, const char *));
71 void            aborthttp __P((int));
72
73
74 #define FTP_URL         "ftp://"        /* ftp URL prefix */
75 #define HTTP_URL        "http://"       /* http URL prefix */
76 #define FTP_PROXY       "ftp_proxy"     /* env var with ftp proxy location */
77 #define HTTP_PROXY      "http_proxy"    /* env var with http proxy location */
78
79
80 #define EMPTYSTRING(x)  ((x) == NULL || (*(x) == '\0'))
81
82 jmp_buf httpabort;
83
84 /*
85  * Retrieve URL, via the proxy in $proxyvar if necessary.
86  * Modifies the string argument given.
87  * Returns -1 on failure, 0 on success
88  */
89 static int
90 url_get(origline, proxyenv)
91         const char *origline;
92         const char *proxyenv;
93 {
94         struct sockaddr_in sin;
95         int i, out, isftpurl;
96         u_int16_t port;
97         volatile int s;
98         size_t len;
99         char c, *cp, *ep, *portnum, *path, buf[4096];
100         const char *savefile;
101         char *line, *proxy, *host;
102         volatile sig_t oldintr;
103         off_t hashbytes;
104
105         s = -1;
106         proxy = NULL;
107         isftpurl = 0;
108
109 #ifdef __GNUC__                 /* XXX: to shut up gcc warnings */
110         (void)&savefile;
111         (void)&proxy;
112 #endif
113
114         line = strdup(origline);
115         if (line == NULL)
116                 errx(1, "Can't allocate memory to parse URL");
117         if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
118                 host = line + sizeof(HTTP_URL) - 1;
119         else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
120                 host = line + sizeof(FTP_URL) - 1;
121                 isftpurl = 1;
122         } else
123                 errx(1, "url_get: Invalid URL '%s'", line);
124
125         path = strchr(host, '/');               /* find path */
126         if (EMPTYSTRING(path)) {
127                 if (isftpurl)
128                         goto noftpautologin;
129                 warnx("Invalid URL (no `/' after host): %s", origline);
130                 goto cleanup_url_get;
131         }
132         *path++ = '\0';
133         if (EMPTYSTRING(path)) {
134                 if (isftpurl)
135                         goto noftpautologin;
136                 warnx("Invalid URL (no file after host): %s", origline);
137                 goto cleanup_url_get;
138         }
139
140         savefile = strrchr(path, '/');                  /* find savefile */
141         if (savefile != NULL)
142                 savefile++;
143         else
144                 savefile = path;
145         if (EMPTYSTRING(savefile)) {
146                 if (isftpurl)
147                         goto noftpautologin;
148                 warnx("Invalid URL (no file after directory): %s", origline);
149                 goto cleanup_url_get;
150         }
151
152         if (proxyenv != NULL) {                         /* use proxy */
153                 proxy = strdup(proxyenv);
154                 if (proxy == NULL)
155                         errx(1, "Can't allocate memory for proxy URL.");
156                 if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
157                         host = proxy + sizeof(HTTP_URL) - 1;
158                 else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
159                         host = proxy + sizeof(FTP_URL) - 1;
160                 else {
161                         warnx("Malformed proxy URL: %s", proxyenv);
162                         goto cleanup_url_get;
163                 }
164                 if (EMPTYSTRING(host)) {
165                         warnx("Malformed proxy URL: %s", proxyenv);
166                         goto cleanup_url_get;
167                 }
168                 *--path = '/';                  /* add / back to real path */
169                 path = strchr(host, '/');       /* remove trailing / on host */
170                 if (! EMPTYSTRING(path))
171                         *path++ = '\0';
172                 path = line;
173         }
174
175         portnum = strchr(host, ':');                    /* find portnum */
176         if (portnum != NULL)
177                 *portnum++ = '\0';
178
179         if (debug)
180                 printf("host %s, port %s, path %s, save as %s.\n",
181                     host, portnum, path, savefile);
182
183         memset(&sin, 0, sizeof(sin));
184         sin.sin_family = AF_INET;
185
186         if (isdigit((unsigned char)host[0])) {
187                 if (inet_aton(host, &sin.sin_addr) == 0) {
188                         warnx("Invalid IP address: %s", host);
189                         goto cleanup_url_get;
190                 }
191         } else {
192                 struct hostent *hp;
193
194                 hp = gethostbyname(host);
195                 if (hp == NULL) {
196                         warnx("%s: %s", host, hstrerror(h_errno));
197                         goto cleanup_url_get;
198                 }
199                 if (hp->h_addrtype != AF_INET) {
200                         warnx("%s: not an Internet address?", host);
201                         goto cleanup_url_get;
202                 }
203                 memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
204         }
205
206         if (! EMPTYSTRING(portnum)) {
207                 char *ep;
208                 long nport;
209
210                 nport = strtol(portnum, &ep, 10);
211                 if (nport < 1 || nport > 0xffff || *ep != '\0') {
212                         warnx("Invalid port: %s", portnum);
213                         goto cleanup_url_get;
214                 }
215                 port = htons(nport);
216         } else
217                 port = httpport;
218         sin.sin_port = port;
219
220         s = socket(AF_INET, SOCK_STREAM, 0);
221         if (s == -1) {
222                 warn("Can't create socket");
223                 goto cleanup_url_get;
224         }
225
226         if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
227                 warn("Can't connect to %s", host);
228                 goto cleanup_url_get;
229         }
230
231         /*
232          * Construct and send the request.  We're expecting a return
233          * status of "200". Proxy requests don't want leading /.
234          */
235         if (!proxy)
236                 printf("Requesting %s\n", origline);
237         else
238                 printf("Requesting %s (via %s)\n", origline, proxyenv);
239         len = snprintf(buf, sizeof(buf), "GET %s%s HTTP/1.0\r\n\r\n",
240             proxy ? "" : "/", path);
241         if (write(s, buf, len) < len) {
242                 warn("Writing HTTP request");
243                 goto cleanup_url_get;
244         }
245         memset(buf, 0, sizeof(buf));
246         for (cp = buf; cp < buf + sizeof(buf); ) {
247                 if (read(s, cp, 1) != 1)
248                         goto improper;
249                 if (*cp == '\r')
250                         continue;
251                 if (*cp == '\n')
252                         break;
253                 cp++;
254         }
255         buf[sizeof(buf) - 1] = '\0';            /* sanity */
256         cp = strchr(buf, ' ');
257         if (cp == NULL)
258                 goto improper;
259         else
260                 cp++;
261         if (strncmp(cp, "200", 3)) {
262                 warnx("Error retrieving file: %s", cp);
263                 goto cleanup_url_get;
264         }
265
266         /*
267          * Read the rest of the header.
268          */
269         memset(buf, 0, sizeof(buf));
270         c = '\0';
271         for (cp = buf; cp < buf + sizeof(buf); ) {
272                 if (read(s, cp, 1) != 1)
273                         goto improper;
274                 if (*cp == '\r')
275                         continue;
276                 if (*cp == '\n' && c == '\n')
277                         break;
278                 c = *cp;
279                 cp++;
280         }
281         buf[sizeof(buf) - 1] = '\0';            /* sanity */
282
283         /*
284          * Look for the "Content-length: " header.
285          */
286 #define CONTENTLEN "Content-Length: "
287         for (cp = buf; *cp != '\0'; cp++) {
288                 if (tolower((unsigned char)*cp) == 'c' &&
289                     strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
290                         break;
291         }
292         if (*cp != '\0') {
293                 cp += sizeof(CONTENTLEN) - 1;
294                 ep = strchr(cp, '\n');
295                 if (ep == NULL)
296                         goto improper;
297                 else
298                         *ep = '\0';
299                 filesize = strtol(cp, &ep, 10);
300                 if (filesize < 1 || *ep != '\0')
301                         goto improper;
302         } else
303                 filesize = -1;
304
305         /* Open the output file. */
306         out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
307         if (out < 0) {
308                 warn("Can't open %s", savefile);
309                 goto cleanup_url_get;
310         }
311
312         /* Trap signals */
313         oldintr = NULL;
314         if (setjmp(httpabort)) {
315                 if (oldintr)
316                         (void)signal(SIGINT, oldintr);
317                 goto cleanup_url_get;
318         }
319         oldintr = signal(SIGINT, aborthttp);
320
321         bytes = 0;
322         hashbytes = mark;
323         progressmeter(-1);
324
325         /* Finally, suck down the file. */
326         i = 0;
327         while ((len = read(s, buf, sizeof(buf))) > 0) {
328                 bytes += len;
329                 for (cp = buf; len > 0; len -= i, cp += i) {
330                         if ((i = write(out, cp, len)) == -1) {
331                                 warn("Writing %s", savefile);
332                                 goto cleanup_url_get;
333                         }
334                         else if (i == 0)
335                                 break;
336                 }
337                 if (hash && !progress) {
338                         while (bytes >= hashbytes) {
339                                 (void)putchar('#');
340                                 hashbytes += mark;
341                         }
342                         (void)fflush(stdout);
343                 }
344         }
345         if (hash && !progress && bytes > 0) {
346                 if (bytes < mark)
347                         (void)putchar('#');
348                 (void)putchar('\n');
349                 (void)fflush(stdout);
350         }
351         if (len != 0) {
352                 warn("Reading from socket");
353                 goto cleanup_url_get;
354         }
355         progressmeter(1);
356         if (verbose)
357                 puts("Successfully retrieved file.");
358         (void)signal(SIGINT, oldintr);
359
360         close(s);
361         close(out);
362         if (proxy)
363                 free(proxy);
364         free(line);
365         return (0);
366
367 noftpautologin:
368         warnx(
369             "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
370         goto cleanup_url_get;
371
372 improper:
373         warnx("Improper response from %s", host);
374
375 cleanup_url_get:
376         if (s != -1)
377                 close(s);
378         if (proxy)
379                 free(proxy);
380         free(line);
381         return (-1);
382 }
383
384 /*
385  * Abort a http retrieval
386  */
387 void
388 aborthttp(notused)
389         int notused;
390 {
391
392         alarmtimer(0);
393         puts("\nhttp fetch aborted.");
394         (void)fflush(stdout);
395         longjmp(httpabort, 1);
396 }
397
398 /*
399  * Retrieve multiple files from the command line, transferring
400  * files of the form "host:path", "ftp://host/path" using the
401  * ftp protocol, and files of the form "http://host/path" using
402  * the http protocol.
403  * If path has a trailing "/", then return (-1);
404  * the path will be cd-ed into and the connection remains open,
405  * and the function will return -1 (to indicate the connection
406  * is alive).
407  * If an error occurs the return value will be the offset+1 in
408  * argv[] of the file that caused a problem (i.e, argv[x]
409  * returns x+1)
410  * Otherwise, 0 is returned if all files retrieved successfully.
411  */
412 int
413 auto_fetch(argc, argv)
414         int argc;
415         char *argv[];
416 {
417         static char lasthost[MAXHOSTNAMELEN];
418         char *xargv[5];
419         char *cp, *line, *host, *dir, *file, *portnum;
420         char *user, *pass;
421         char *ftpproxy, *httpproxy;
422         int rval, xargc;
423         volatile int argpos;
424         int dirhasglob, filehasglob;
425         char rempath[MAXPATHLEN];
426
427         argpos = 0;
428
429         if (setjmp(toplevel)) {
430                 if (connected)
431                         disconnect(0, NULL);
432                 return (argpos + 1);
433         }
434         (void)signal(SIGINT, (sig_t)intr);
435         (void)signal(SIGPIPE, (sig_t)lostpeer);
436
437         ftpproxy = getenv(FTP_PROXY);
438         httpproxy = getenv(HTTP_PROXY);
439
440         /*
441          * Loop through as long as there's files to fetch.
442          */
443         for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
444                 if (strchr(argv[argpos], ':') == NULL)
445                         break;
446                 host = dir = file = portnum = user = pass = NULL;
447
448                 /*
449                  * We muck with the string, so we make a copy.
450                  */
451                 line = strdup(argv[argpos]);
452                 if (line == NULL)
453                         errx(1, "Can't allocate memory for auto-fetch.");
454
455                 /*
456                  * Try HTTP URL-style arguments first.
457                  */
458                 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
459                         if (url_get(line, httpproxy) == -1)
460                                 rval = argpos + 1;
461                         continue;
462                 }
463
464                 /*
465                  * Try FTP URL-style arguments next. If ftpproxy is
466                  * set, use url_get() instead of standard ftp.
467                  * Finally, try host:file.
468                  */
469                 host = line;
470                 if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
471                         if (ftpproxy) {
472                                 if (url_get(line, ftpproxy) == -1)
473                                         rval = argpos + 1;
474                                 continue;
475                         }
476                         host += sizeof(FTP_URL) - 1;
477                         dir = strchr(host, '/');
478
479                                 /* look for [user:pass@]host[:port] */
480                         pass = strpbrk(host, ":@/");
481                         if (pass == NULL || *pass == '/') {
482                                 pass = NULL;
483                                 goto parsed_url;
484                         }
485                         if (pass == host || *pass == '@') {
486 bad_ftp_url:
487                                 warnx("Invalid URL: %s", argv[argpos]);
488                                 rval = argpos + 1;
489                                 continue;
490                         }
491                         *pass++ = '\0';
492                         cp = strpbrk(pass, ":@/");
493                         if (cp == NULL || *cp == '/') {
494                                 portnum = pass;
495                                 pass = NULL;
496                                 goto parsed_url;
497                         }
498                         if (EMPTYSTRING(cp) || *cp == ':')
499                                 goto bad_ftp_url;
500                         *cp++ = '\0';
501                         user = host;
502                         if (EMPTYSTRING(user))
503                                 goto bad_ftp_url;
504                         host = cp;
505                         portnum = strchr(host, ':');
506                         if (portnum != NULL)
507                                 *portnum++ = '\0';
508                 } else {                        /* classic style `host:file' */
509                         dir = strchr(host, ':');
510                 }
511 parsed_url:
512                 if (EMPTYSTRING(host)) {
513                         rval = argpos + 1;
514                         continue;
515                 }
516
517                 /*
518                  * If dir is NULL, the file wasn't specified
519                  * (URL looked something like ftp://host)
520                  */
521                 if (dir != NULL)
522                         *dir++ = '\0';
523
524                 /*
525                  * Extract the file and (if present) directory name.
526                  */
527                 if (! EMPTYSTRING(dir)) {
528                         cp = strrchr(dir, '/');
529                         if (cp != NULL) {
530                                 *cp++ = '\0';
531                                 file = cp;
532                         } else {
533                                 file = dir;
534                                 dir = NULL;
535                         }
536                 }
537                 if (debug)
538                         printf("user %s:%s host %s port %s dir %s file %s\n",
539                             user, pass, host, portnum, dir, file);
540
541                 /*
542                  * Set up the connection if we don't have one.
543                  */
544                 if (strcmp(host, lasthost) != 0) {
545                         int oautologin;
546
547                         (void)strcpy(lasthost, host);
548                         if (connected)
549                                 disconnect(0, NULL);
550                         xargv[0] = __progname;
551                         xargv[1] = host;
552                         xargv[2] = NULL;
553                         xargc = 2;
554                         if (! EMPTYSTRING(portnum)) {
555                                 xargv[2] = portnum;
556                                 xargv[3] = NULL;
557                                 xargc = 3;
558                         }
559                         oautologin = autologin;
560                         if (user != NULL)
561                                 autologin = 0;
562                         setpeer(xargc, xargv);
563                         autologin = oautologin;
564                         if ((connected == 0)
565                          || ((connected == 1) && !login(host, user, pass)) ) {
566                                 warnx("Can't connect or login to host `%s'",
567                                     host);
568                                 rval = argpos + 1;
569                                 continue;
570                         }
571
572                         /* Always use binary transfers. */
573                         setbinary(0, NULL);
574                 }
575                         /* cd back to '/' */
576                 xargv[0] = "cd";
577                 xargv[1] = "/";
578                 xargv[2] = NULL;
579                 cd(2, xargv);
580                 if (! dirchange) {
581                         rval = argpos + 1;
582                         continue;
583                 }
584
585                 dirhasglob = filehasglob = 0;
586                 if (doglob) {
587                         if (! EMPTYSTRING(dir) &&
588                             strpbrk(dir, "*?[]{}") != NULL)
589                                 dirhasglob = 1;
590                         if (! EMPTYSTRING(file) &&
591                             strpbrk(file, "*?[]{}") != NULL)
592                                 filehasglob = 1;
593                 }
594
595                 /* Change directories, if necessary. */
596                 if (! EMPTYSTRING(dir) && !dirhasglob) {
597                         xargv[0] = "cd";
598                         xargv[1] = dir;
599                         xargv[2] = NULL;
600                         cd(2, xargv);
601                         if (! dirchange) {
602                                 rval = argpos + 1;
603                                 continue;
604                         }
605                 }
606
607                 if (EMPTYSTRING(file)) {
608                         rval = -1;
609                         continue;
610                 }
611
612                 if (!verbose)
613                         printf("Retrieving %s/%s\n", dir ? dir : "", file);
614
615                 if (dirhasglob) {
616                         snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
617                         file = rempath;
618                 }
619
620                 /* Fetch the file(s). */
621                 xargv[0] = "get";
622                 xargv[1] = file;
623                 xargv[2] = NULL;
624                 if (dirhasglob || filehasglob) {
625                         int ointeractive;
626
627                         ointeractive = interactive;
628                         interactive = 0;
629                         xargv[0] = "mget";
630                         mget(2, xargv);
631                         interactive = ointeractive;
632                 } else
633                         get(2, xargv);
634
635                 if ((code / 100) != COMPLETE)
636                         rval = argpos + 1;
637         }
638         if (connected && rval != -1)
639                 disconnect(0, NULL);
640         return (rval);
641 }