Commit | Line | Data |
---|---|---|
edf71f48 DF |
1 | /* |
2 | * Copyright (c) 1980 Regents of the University of California. | |
3 | * All rights reserved. The Berkeley software License Agreement | |
4 | * specifies the terms and conditions for redistribution. | |
5 | */ | |
6 | ||
e0b077fe | 7 | #ifndef lint |
edf71f48 DF |
8 | static char sccsid[] = "@(#)ftp.c 5.1 (Berkeley) %G%"; |
9 | #endif not lint | |
e0b077fe SL |
10 | |
11 | #include <sys/param.h> | |
12 | #include <sys/stat.h> | |
13 | #include <sys/ioctl.h> | |
14 | #include <sys/socket.h> | |
19371098 | 15 | #include <sys/time.h> |
e0b077fe SL |
16 | |
17 | #include <netinet/in.h> | |
62c4a390 | 18 | #include <arpa/ftp.h> |
e0b077fe SL |
19 | |
20 | #include <stdio.h> | |
21 | #include <signal.h> | |
e0b077fe SL |
22 | #include <errno.h> |
23 | #include <netdb.h> | |
24 | ||
e0b077fe SL |
25 | #include "ftp_var.h" |
26 | ||
27 | struct sockaddr_in hisctladdr; | |
28 | struct sockaddr_in data_addr; | |
29 | int data = -1; | |
30 | int connected; | |
31 | struct sockaddr_in myctladdr; | |
32 | ||
33 | FILE *cin, *cout; | |
34 | FILE *dataconn(); | |
35 | ||
36 | struct hostent * | |
37 | hookup(host, port) | |
38 | char *host; | |
39 | int port; | |
40 | { | |
41 | register struct hostent *hp; | |
8c1d51c7 | 42 | int s, len; |
e0b077fe SL |
43 | |
44 | bzero((char *)&hisctladdr, sizeof (hisctladdr)); | |
45 | hp = gethostbyname(host); | |
6e88ca41 | 46 | if (hp == NULL) { |
e0b077fe SL |
47 | static struct hostent def; |
48 | static struct in_addr defaddr; | |
49 | static char namebuf[128]; | |
50 | int inet_addr(); | |
51 | ||
52 | defaddr.s_addr = inet_addr(host); | |
53 | if (defaddr.s_addr == -1) { | |
54 | fprintf(stderr, "%s: Unknown host.\n", host); | |
55 | return (0); | |
56 | } | |
57 | strcpy(namebuf, host); | |
58 | def.h_name = namebuf; | |
59 | hostname = namebuf; | |
60 | def.h_addr = (char *)&defaddr; | |
61 | def.h_length = sizeof (struct in_addr); | |
62 | def.h_addrtype = AF_INET; | |
63 | def.h_aliases = 0; | |
64 | hp = &def; | |
65 | } | |
6e88ca41 SL |
66 | hostname = hp->h_name; |
67 | hisctladdr.sin_family = hp->h_addrtype; | |
71a655cd | 68 | s = socket(hp->h_addrtype, SOCK_STREAM, 0); |
e0b077fe SL |
69 | if (s < 0) { |
70 | perror("ftp: socket"); | |
71 | return (0); | |
72 | } | |
73 | if (bind(s, (char *)&hisctladdr, sizeof (hisctladdr), 0) < 0) { | |
74 | perror("ftp: bind"); | |
75 | goto bad; | |
76 | } | |
77 | bcopy(hp->h_addr, (char *)&hisctladdr.sin_addr, hp->h_length); | |
78 | hisctladdr.sin_port = port; | |
79 | if (connect(s, (char *)&hisctladdr, sizeof (hisctladdr), 0) < 0) { | |
80 | perror("ftp: connect"); | |
81 | goto bad; | |
82 | } | |
8c1d51c7 SL |
83 | len = sizeof (myctladdr); |
84 | if (getsockname(s, (char *)&myctladdr, &len) < 0) { | |
85 | perror("ftp: getsockname"); | |
e0b077fe SL |
86 | goto bad; |
87 | } | |
88 | cin = fdopen(s, "r"); | |
89 | cout = fdopen(s, "w"); | |
d33c618b | 90 | if (cin == NULL || cout == NULL) { |
e0b077fe SL |
91 | fprintf(stderr, "ftp: fdopen failed.\n"); |
92 | if (cin) | |
93 | fclose(cin); | |
94 | if (cout) | |
95 | fclose(cout); | |
96 | goto bad; | |
97 | } | |
98 | if (verbose) | |
99 | printf("Connected to %s.\n", hp->h_name); | |
100 | (void) getreply(0); /* read startup message from server */ | |
101 | return (hp); | |
102 | bad: | |
103 | close(s); | |
104 | return ((struct hostent *)0); | |
105 | } | |
106 | ||
107 | login(hp) | |
108 | struct hostent *hp; | |
109 | { | |
110 | char acct[80]; | |
111 | char *user, *pass; | |
112 | int n; | |
113 | ||
114 | user = pass = 0; | |
115 | ruserpass(hp->h_name, &user, &pass); | |
116 | n = command("USER %s", user); | |
117 | if (n == CONTINUE) | |
118 | n = command("PASS %s", pass); | |
119 | if (n == CONTINUE) { | |
120 | printf("Account: "); (void) fflush(stdout); | |
121 | (void) fgets(acct, sizeof(acct) - 1, stdin); | |
122 | acct[strlen(acct) - 1] = '\0'; | |
123 | n = command("ACCT %s", acct); | |
124 | } | |
125 | if (n != COMPLETE) { | |
126 | fprintf(stderr, "Login failed.\n"); | |
127 | return (0); | |
128 | } | |
129 | return (1); | |
130 | } | |
131 | ||
132 | /*VARARGS 1*/ | |
133 | command(fmt, args) | |
134 | char *fmt; | |
135 | { | |
136 | ||
137 | if (debug) { | |
138 | printf("---> "); | |
139 | _doprnt(fmt, &args, stdout); | |
140 | printf("\n"); | |
141 | (void) fflush(stdout); | |
142 | } | |
d33c618b SL |
143 | if (cout == NULL) { |
144 | perror ("No control connection for command"); | |
145 | return (0); | |
146 | } | |
e0b077fe SL |
147 | _doprnt(fmt, &args, cout); |
148 | fprintf(cout, "\r\n"); | |
149 | (void) fflush(cout); | |
150 | return (getreply(!strcmp(fmt, "QUIT"))); | |
151 | } | |
152 | ||
153 | #include <ctype.h> | |
154 | ||
155 | getreply(expecteof) | |
156 | int expecteof; | |
157 | { | |
d33c618b | 158 | register int c, n; |
e0b077fe SL |
159 | register int code, dig; |
160 | int originalcode = 0, continuation = 0; | |
161 | ||
162 | for (;;) { | |
163 | dig = n = code = 0; | |
164 | while ((c = getc(cin)) != '\n') { | |
165 | dig++; | |
166 | if (c == EOF) { | |
167 | if (expecteof) | |
168 | return (0); | |
169 | lostpeer(); | |
170 | exit(1); | |
171 | } | |
172 | if (verbose && c != '\r' || | |
173 | (n == '5' && dig > 4)) | |
174 | putchar(c); | |
175 | if (dig < 4 && isdigit(c)) | |
176 | code = code * 10 + (c - '0'); | |
177 | if (dig == 4 && c == '-') | |
178 | continuation++; | |
179 | if (n == 0) | |
180 | n = c; | |
181 | } | |
7968a79f | 182 | if (verbose || n == '5') { |
e0b077fe | 183 | putchar(c); |
7968a79f SL |
184 | (void) fflush (stdout); |
185 | } | |
e0b077fe SL |
186 | if (continuation && code != originalcode) { |
187 | if (originalcode == 0) | |
188 | originalcode = code; | |
189 | continue; | |
190 | } | |
d33c618b | 191 | if (expecteof || empty(cin)) |
e0b077fe SL |
192 | return (n - '0'); |
193 | } | |
194 | } | |
195 | ||
196 | empty(f) | |
197 | FILE *f; | |
198 | { | |
5ac6fc46 | 199 | long mask; |
e0b077fe SL |
200 | struct timeval t; |
201 | ||
202 | if (f->_cnt > 0) | |
203 | return (0); | |
204 | mask = (1 << fileno(f)); | |
205 | t.tv_sec = t.tv_usec = 0; | |
206 | (void) select(20, &mask, 0, 0, &t); | |
207 | return (mask == 0); | |
208 | } | |
209 | ||
210 | jmp_buf sendabort; | |
211 | ||
212 | abortsend() | |
213 | { | |
214 | ||
215 | longjmp(sendabort, 1); | |
216 | } | |
217 | ||
218 | sendrequest(cmd, local, remote) | |
219 | char *cmd, *local, *remote; | |
220 | { | |
221 | FILE *fin, *dout, *popen(); | |
222 | int (*closefunc)(), pclose(), fclose(), (*oldintr)(); | |
d33c618b | 223 | char buf[BUFSIZ]; |
5ac6fc46 | 224 | long bytes = 0, hashbytes = sizeof (buf); |
7968a79f | 225 | register int c, d; |
e0b077fe SL |
226 | struct stat st; |
227 | struct timeval start, stop; | |
228 | ||
229 | closefunc = NULL; | |
230 | if (setjmp(sendabort)) | |
231 | goto bad; | |
232 | oldintr = signal(SIGINT, abortsend); | |
233 | if (strcmp(local, "-") == 0) | |
234 | fin = stdin; | |
235 | else if (*local == '|') { | |
236 | fin = popen(local + 1, "r"); | |
237 | if (fin == NULL) { | |
238 | perror(local + 1); | |
239 | goto bad; | |
240 | } | |
241 | closefunc = pclose; | |
242 | } else { | |
243 | fin = fopen(local, "r"); | |
244 | if (fin == NULL) { | |
245 | perror(local); | |
246 | goto bad; | |
247 | } | |
248 | closefunc = fclose; | |
249 | if (fstat(fileno(fin), &st) < 0 || | |
250 | (st.st_mode&S_IFMT) != S_IFREG) { | |
b4b22f0c | 251 | fprintf(stderr, "%s: not a plain file.\n", local); |
e0b077fe SL |
252 | goto bad; |
253 | } | |
254 | } | |
255 | if (initconn()) | |
256 | goto bad; | |
257 | if (remote) { | |
258 | if (command("%s %s", cmd, remote) != PRELIM) | |
259 | goto bad; | |
260 | } else | |
261 | if (command("%s", cmd) != PRELIM) | |
262 | goto bad; | |
263 | dout = dataconn("w"); | |
264 | if (dout == NULL) | |
265 | goto bad; | |
266 | gettimeofday(&start, (struct timezone *)0); | |
d33c618b SL |
267 | switch (type) { |
268 | ||
269 | case TYPE_I: | |
270 | case TYPE_L: | |
7968a79f | 271 | errno = d = 0; |
d33c618b | 272 | while ((c = read(fileno (fin), buf, sizeof (buf))) > 0) { |
7968a79f | 273 | if ((d = write(fileno (dout), buf, c)) < 0) |
d33c618b SL |
274 | break; |
275 | bytes += c; | |
5ac6fc46 SL |
276 | if (hash) { |
277 | putchar('#'); | |
278 | fflush(stdout); | |
279 | } | |
280 | } | |
614e24b6 | 281 | if (hash && bytes > 0) { |
5ac6fc46 SL |
282 | putchar('\n'); |
283 | fflush(stdout); | |
d33c618b SL |
284 | } |
285 | if (c < 0) | |
286 | perror(local); | |
7968a79f | 287 | if (d < 0) |
d33c618b SL |
288 | perror("netout"); |
289 | break; | |
290 | ||
291 | case TYPE_A: | |
292 | while ((c = getc(fin)) != EOF) { | |
293 | if (c == '\n') { | |
5ac6fc46 SL |
294 | while (hash && (bytes >= hashbytes)) { |
295 | putchar('#'); | |
296 | fflush(stdout); | |
297 | hashbytes += sizeof (buf); | |
298 | } | |
d33c618b SL |
299 | if (ferror(dout)) |
300 | break; | |
301 | putc('\r', dout); | |
302 | bytes++; | |
303 | } | |
304 | putc(c, dout); | |
305 | bytes++; | |
306 | if (c == '\r') { | |
307 | putc('\0', dout); | |
308 | bytes++; | |
309 | } | |
310 | } | |
5ac6fc46 | 311 | if (hash) { |
614e24b6 SL |
312 | if (bytes < hashbytes) |
313 | putchar('#'); | |
5ac6fc46 SL |
314 | putchar('\n'); |
315 | fflush(stdout); | |
316 | } | |
d33c618b SL |
317 | if (ferror(fin)) |
318 | perror(local); | |
7968a79f | 319 | if (ferror(dout)) |
d33c618b SL |
320 | perror("netout"); |
321 | break; | |
e0b077fe SL |
322 | } |
323 | gettimeofday(&stop, (struct timezone *)0); | |
324 | if (closefunc != NULL) | |
325 | (*closefunc)(fin); | |
326 | (void) fclose(dout); | |
e0b077fe SL |
327 | (void) getreply(0); |
328 | done: | |
329 | signal(SIGINT, oldintr); | |
330 | if (bytes > 0 && verbose) | |
331 | ptransfer("sent", bytes, &start, &stop); | |
332 | return; | |
333 | bad: | |
334 | if (data >= 0) | |
335 | (void) close(data), data = -1; | |
336 | if (closefunc != NULL && fin != NULL) | |
337 | (*closefunc)(fin); | |
338 | goto done; | |
339 | } | |
340 | ||
341 | jmp_buf recvabort; | |
342 | ||
343 | abortrecv() | |
344 | { | |
345 | ||
346 | longjmp(recvabort, 1); | |
347 | } | |
348 | ||
5ac6fc46 SL |
349 | recvrequest(cmd, local, remote, mode) |
350 | char *cmd, *local, *remote, *mode; | |
e0b077fe SL |
351 | { |
352 | FILE *fout, *din, *popen(); | |
7968a79f | 353 | int (*closefunc)(), pclose(), fclose(), (*oldintr)(); |
5ac6fc46 SL |
354 | char buf[BUFSIZ]; |
355 | long bytes = 0, hashbytes = sizeof (buf); | |
7968a79f | 356 | register int c, d; |
e0b077fe SL |
357 | struct timeval start, stop; |
358 | ||
359 | closefunc = NULL; | |
360 | if (setjmp(recvabort)) | |
361 | goto bad; | |
362 | oldintr = signal(SIGINT, abortrecv); | |
363 | if (strcmp(local, "-") && *local != '|') | |
364 | if (access(local, 2) < 0) { | |
365 | char *dir = rindex(local, '/'); | |
366 | ||
367 | if (dir != NULL) | |
368 | *dir = 0; | |
f0773a1d RC |
369 | d = access(dir ? local : ".", 2); |
370 | if (dir != NULL) | |
371 | *dir = '/'; | |
372 | if (d < 0) { | |
e0b077fe SL |
373 | perror(local); |
374 | goto bad; | |
375 | } | |
e0b077fe SL |
376 | } |
377 | if (initconn()) | |
378 | goto bad; | |
379 | if (remote) { | |
380 | if (command("%s %s", cmd, remote) != PRELIM) | |
381 | goto bad; | |
382 | } else | |
383 | if (command("%s", cmd) != PRELIM) | |
384 | goto bad; | |
385 | if (strcmp(local, "-") == 0) | |
386 | fout = stdout; | |
387 | else if (*local == '|') { | |
388 | fout = popen(local + 1, "w"); | |
389 | closefunc = pclose; | |
390 | } else { | |
5ac6fc46 | 391 | fout = fopen(local, mode); |
e0b077fe SL |
392 | closefunc = fclose; |
393 | } | |
394 | if (fout == NULL) { | |
395 | perror(local + 1); | |
396 | goto bad; | |
397 | } | |
398 | din = dataconn("r"); | |
399 | if (din == NULL) | |
400 | goto bad; | |
401 | gettimeofday(&start, (struct timezone *)0); | |
d33c618b SL |
402 | switch (type) { |
403 | ||
404 | case TYPE_I: | |
405 | case TYPE_L: | |
7968a79f | 406 | errno = d = 0; |
d33c618b | 407 | while ((c = read(fileno(din), buf, sizeof (buf))) > 0) { |
7968a79f | 408 | if ((d = write(fileno(fout), buf, c)) < 0) |
d33c618b SL |
409 | break; |
410 | bytes += c; | |
5ac6fc46 SL |
411 | if (hash) { |
412 | putchar('#'); | |
413 | fflush(stdout); | |
414 | } | |
415 | } | |
614e24b6 | 416 | if (hash && bytes > 0) { |
5ac6fc46 SL |
417 | putchar('\n'); |
418 | fflush(stdout); | |
d33c618b SL |
419 | } |
420 | if (c < 0) | |
421 | perror("netin"); | |
7968a79f | 422 | if (d < 0) |
e0b077fe | 423 | perror(local); |
d33c618b SL |
424 | break; |
425 | ||
426 | case TYPE_A: | |
427 | while ((c = getc(din)) != EOF) { | |
428 | if (c == '\r') { | |
5ac6fc46 SL |
429 | while (hash && (bytes >= hashbytes)) { |
430 | putchar('#'); | |
431 | fflush(stdout); | |
432 | hashbytes += sizeof (buf); | |
433 | } | |
e0b077fe | 434 | bytes++; |
d33c618b SL |
435 | if ((c = getc(din)) != '\n') { |
436 | if (ferror (fout)) | |
437 | break; | |
438 | putc ('\r', fout); | |
439 | } | |
440 | if (c == '\0') { | |
441 | bytes++; | |
442 | continue; | |
443 | } | |
444 | } | |
445 | putc (c, fout); | |
446 | bytes++; | |
e0b077fe | 447 | } |
5ac6fc46 | 448 | if (hash) { |
614e24b6 SL |
449 | if (bytes < hashbytes) |
450 | putchar('#'); | |
5ac6fc46 SL |
451 | putchar('\n'); |
452 | fflush(stdout); | |
453 | } | |
d33c618b SL |
454 | if (ferror (din)) |
455 | perror ("netin"); | |
456 | if (ferror (fout)) | |
457 | perror (local); | |
458 | break; | |
e0b077fe SL |
459 | } |
460 | gettimeofday(&stop, (struct timezone *)0); | |
461 | (void) fclose(din); | |
462 | if (closefunc != NULL) | |
463 | (*closefunc)(fout); | |
464 | (void) getreply(0); | |
465 | done: | |
466 | signal(SIGINT, oldintr); | |
467 | if (bytes > 0 && verbose) | |
468 | ptransfer("received", bytes, &start, &stop); | |
469 | return; | |
470 | bad: | |
471 | if (data >= 0) | |
472 | (void) close(data), data = -1; | |
473 | if (closefunc != NULL && fout != NULL) | |
474 | (*closefunc)(fout); | |
475 | goto done; | |
476 | } | |
477 | ||
478 | /* | |
479 | * Need to start a listen on the data channel | |
480 | * before we send the command, otherwise the | |
481 | * server's connect may fail. | |
482 | */ | |
5ac6fc46 SL |
483 | static int sendport = -1; |
484 | ||
e0b077fe SL |
485 | initconn() |
486 | { | |
487 | register char *p, *a; | |
8c1d51c7 | 488 | int result, len; |
0975b26d | 489 | int on = 1; |
e0b077fe | 490 | |
5ac6fc46 | 491 | noport: |
e0b077fe | 492 | data_addr = myctladdr; |
5ac6fc46 SL |
493 | if (sendport) |
494 | data_addr.sin_port = 0; /* let system pick one */ | |
495 | if (data != -1) | |
496 | (void) close (data); | |
71a655cd | 497 | data = socket(AF_INET, SOCK_STREAM, 0); |
e0b077fe SL |
498 | if (data < 0) { |
499 | perror("ftp: socket"); | |
500 | return (1); | |
501 | } | |
62c4a390 | 502 | if (!sendport) |
0975b26d | 503 | if (setsockopt(data, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0) { |
62c4a390 SL |
504 | perror("ftp: setsockopt (resuse address)"); |
505 | goto bad; | |
506 | } | |
e0b077fe SL |
507 | if (bind(data, (char *)&data_addr, sizeof (data_addr), 0) < 0) { |
508 | perror("ftp: bind"); | |
509 | goto bad; | |
510 | } | |
e0b077fe | 511 | if (options & SO_DEBUG && |
0975b26d | 512 | setsockopt(data, SOL_SOCKET, SO_DEBUG, &on, sizeof (on)) < 0) |
e0b077fe | 513 | perror("ftp: setsockopt (ignored)"); |
8c1d51c7 SL |
514 | len = sizeof (data_addr); |
515 | if (getsockname(data, (char *)&data_addr, &len) < 0) { | |
516 | perror("ftp: getsockname"); | |
e0b077fe SL |
517 | goto bad; |
518 | } | |
519 | if (listen(data, 1) < 0) { | |
520 | perror("ftp: listen"); | |
521 | goto bad; | |
522 | } | |
5ac6fc46 SL |
523 | if (sendport) { |
524 | a = (char *)&data_addr.sin_addr; | |
525 | p = (char *)&data_addr.sin_port; | |
e0b077fe | 526 | #define UC(b) (((int)b)&0xff) |
5ac6fc46 SL |
527 | result = |
528 | command("PORT %d,%d,%d,%d,%d,%d", | |
529 | UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), | |
530 | UC(p[0]), UC(p[1])); | |
531 | if (result == ERROR && sendport == -1) { | |
532 | sendport = 0; | |
533 | goto noport; | |
534 | } | |
535 | return (result != COMPLETE); | |
536 | } | |
537 | return (0); | |
e0b077fe SL |
538 | bad: |
539 | (void) close(data), data = -1; | |
540 | return (1); | |
541 | } | |
542 | ||
543 | FILE * | |
544 | dataconn(mode) | |
545 | char *mode; | |
546 | { | |
547 | struct sockaddr_in from; | |
548 | int s, fromlen = sizeof (from); | |
549 | ||
550 | s = accept(data, &from, &fromlen, 0); | |
551 | if (s < 0) { | |
552 | perror("ftp: accept"); | |
553 | (void) close(data), data = -1; | |
554 | return (NULL); | |
555 | } | |
556 | (void) close(data); | |
557 | data = s; | |
558 | return (fdopen(data, mode)); | |
559 | } | |
560 | ||
561 | ptransfer(direction, bytes, t0, t1) | |
562 | char *direction; | |
5ac6fc46 | 563 | long bytes; |
e0b077fe SL |
564 | struct timeval *t0, *t1; |
565 | { | |
566 | struct timeval td; | |
6ee561e3 | 567 | float s, bs; |
e0b077fe SL |
568 | |
569 | tvsub(&td, t1, t0); | |
6ee561e3 | 570 | s = td.tv_sec + (td.tv_usec / 1000000.); |
e0b077fe | 571 | #define nz(x) ((x) == 0 ? 1 : (x)) |
6ee561e3 CL |
572 | bs = bytes / nz(s); |
573 | printf("%ld bytes %s in %.2g seconds (%.2g Kbytes/s)\n", | |
574 | bytes, direction, s, bs / 1024.); | |
e0b077fe SL |
575 | } |
576 | ||
577 | tvadd(tsum, t0) | |
578 | struct timeval *tsum, *t0; | |
579 | { | |
580 | ||
581 | tsum->tv_sec += t0->tv_sec; | |
582 | tsum->tv_usec += t0->tv_usec; | |
583 | if (tsum->tv_usec > 1000000) | |
584 | tsum->tv_sec++, tsum->tv_usec -= 1000000; | |
585 | } | |
586 | ||
587 | tvsub(tdiff, t1, t0) | |
588 | struct timeval *tdiff, *t1, *t0; | |
589 | { | |
590 | ||
591 | tdiff->tv_sec = t1->tv_sec - t0->tv_sec; | |
592 | tdiff->tv_usec = t1->tv_usec - t0->tv_usec; | |
593 | if (tdiff->tv_usec < 0) | |
594 | tdiff->tv_sec--, tdiff->tv_usec += 1000000; | |
595 | } |