7d6d72b4722281bd21c9f49c5c343eaea4623f5b
[mmh] / uip / ftpsbr.c
1 /*
2 ** ftpsbr.c -- simple FTP client library
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <h/mime.h>
11
12 #ifdef HAVE_ARPA_FTP_H
13 # include <arpa/ftp.h>
14 #endif
15
16 #define v_debug debugsw
17 #define v_verbose verbosw
18
19 static int ftp_fd = NOTOK;
20 static int data_fd = NOTOK;
21
22 static int v_noise;
23
24 extern int v_debug;
25 extern int v_verbose;
26
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <errno.h>
32
33 #define start_tcp_client(res) \
34         socket(res->ai_family, res->ai_socktype, res->ai_protocol)
35
36 #define join_tcp_server(fd, sock, len) \
37         connect((fd), (struct sockaddr *) (sock), len)
38
39 /*
40 ** prototypes
41 */
42 int ftp_get(char *, char *, char *, char *, char *, char *, int, int);
43 int ftp_trans(char *, char *, char *, char *, char *, char *, char *,
44                 int, int);
45
46 /*
47 ** static prototypes
48 */
49 static int start_tcp_server(struct sockaddr_in *, int, int, int);
50 static void _asnprintf(char *, int, char *, va_list);
51 static int ftp_quit(void);
52 static int ftp_read(char *, char *, char *, int);
53 static int initconn(void);
54 static int dataconn(void);
55 static int command(int arg1, ...);
56 static int vcommand(int, va_list);
57 static int getreply(int, int);
58
59
60 static int
61 start_tcp_server(struct sockaddr_in *sock, int backlog, int opt1, int opt2)
62 {
63         int eindex, sd;
64
65         if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == NOTOK)
66                 return NOTOK;
67
68         if (bind(sd, (struct sockaddr *) sock, sizeof *sock) == NOTOK) {
69                 eindex = errno;
70                 close(sd);
71                 errno = eindex;
72         } else {
73                 listen(sd, backlog);
74         }
75
76         return sd;
77 }
78
79
80 static int __len__;
81
82 #define join_tcp_client(fd,sock) \
83         accept((fd), (struct sockaddr *) (sock), \
84                 (__len__ = sizeof *(sock), &__len__))
85
86 #define read_tcp_socket read
87 #define write_tcp_socket write
88 #define close_tcp_socket close
89
90 static void
91 _asnprintf(char *bp, int len_bp, char *what, va_list ap)
92 {
93         int eindex, len;
94         char *fmt;
95
96         eindex = errno;
97
98         *bp = '\0';
99         fmt = va_arg(ap, char *);
100
101         if (fmt) {
102                 vsnprintf(bp, len_bp, fmt, ap);
103                 len = strlen(bp);
104                 bp += len;
105                 len_bp -= len;
106         }
107
108         if (what) {
109                 char *s;
110
111                 if (*what) {
112                         snprintf(bp, len_bp, " %s: ", what);
113                         len = strlen(bp);
114                         bp += len;
115                         len_bp -= len;
116                 }
117                 if ((s = strerror(eindex)))
118                         strncpy(bp, s, len_bp);
119                 else
120                         snprintf(bp, len_bp, "Error %d", eindex);
121                 bp += strlen(bp);
122         }
123
124         errno = eindex;
125 }
126
127
128 int
129 ftp_get(char *host, char *user, char *password, char *cwd,
130                  char *remote, char *local, int ascii, int stayopen)
131 {
132         return ftp_trans(host, user, password, cwd, remote, local,
133                 "RETR", ascii, stayopen);
134 }
135
136
137 int
138 ftp_trans(char *host, char *user, char *password, char *cwd, char *remote,
139         char *local, char *cmd, int ascii, int stayopen)
140 {
141         int result;
142
143         if (stayopen <= 0) {
144                 result = ftp_quit();
145                 if (host == NULL)
146                         return result;
147         }
148
149         if (ftp_fd == NOTOK) {
150                 struct addrinfo hints, *res;
151
152                 memset(&hints, 0, sizeof(hints));
153 #ifdef AI_ADDRCONFIG
154                 hints.ai_flags = AI_ADDRCONFIG;
155 #endif
156                 hints.ai_family = PF_INET;
157                 hints.ai_socktype = SOCK_STREAM;
158
159                 result = getaddrinfo(host, "ftp", &hints, &res);
160
161                 if (result) {
162                         fprintf(stderr, "%s/ftp: %s\n",
163                                         host, gai_strerror(result));
164                         return NOTOK;
165                 }
166
167                 if ((ftp_fd = start_tcp_client(res)) == NOTOK) {
168                         perror(host);
169                         freeaddrinfo(res);
170                         return NOTOK;
171                 }
172                 if (join_tcp_server(ftp_fd, res->ai_addr, res->ai_addrlen)
173                                 == NOTOK) {
174                         perror(host);
175                         freeaddrinfo(res);
176                         close_tcp_socket(ftp_fd), ftp_fd = NOTOK;
177                         return NOTOK;
178                 }
179                 freeaddrinfo(res);
180                 getreply(1, 0);
181
182                 if (v_verbose) {
183                         fprintf(stdout, "Connected to %s\n", host);
184                         fflush(stdout);
185                 }
186
187                 if (user) {
188                         if ((result = command (0, "USER %s", user))
189                                         == CONTINUE)
190                                 result = command(1, "PASS %s", password);
191                         if (result != COMPLETE) {
192                                 result = NOTOK;
193                                 goto out;
194                         }
195                 }
196
197                 if (remote == NULL)
198                         return OK;
199         }
200
201         if (cwd && ((result = command (0, "CWD %s", cwd)) != COMPLETE
202                         && result != CONTINUE)) {
203                 result = NOTOK;
204                 goto out;
205         }
206
207         if (command(1, ascii ? "TYPE A" : "TYPE I") != COMPLETE) {
208                 result = NOTOK;
209                 goto out;
210         }
211
212         result = ftp_read(remote, local, cmd, ascii);
213
214 out: ;
215         if (result != OK || !stayopen)
216                 ftp_quit();
217
218         return result;
219 }
220
221
222 static int
223 ftp_quit(void)
224 {
225         int n;
226
227         if (ftp_fd == NOTOK)
228                 return OK;
229
230         n = command(1, "QUIT");
231         close_tcp_socket(ftp_fd), ftp_fd = NOTOK;
232         return (n == 0 || n == COMPLETE ? OK : NOTOK);
233 }
234
235 static int
236 ftp_read(char *remote, char *local, char *cmd, int ascii)
237 {
238         int istdio = 0, istore;
239         register int cc;
240         int expectingreply = 0;
241         char buffer[BUFSIZ];
242         FILE *fp = NULL;
243
244         if (initconn() == NOTOK)
245                 goto bad;
246
247         v_noise = v_verbose;
248         if (command(-1, *remote ? "%s %s" : "%s", cmd, remote) != PRELIM)
249                 goto bad;
250
251         expectingreply++;
252         if (dataconn() == NOTOK) {
253 bad: ;
254                 if (fp && !istdio)
255                         fclose(fp);
256                 if (data_fd != NOTOK)
257                         close_tcp_socket(data_fd), data_fd = NOTOK;
258                 if (expectingreply)
259                         getreply(-2, 0);
260
261                 return NOTOK;
262         }
263
264         istore = !strcmp(cmd, "STOR");
265
266         if ((istdio = !strcmp(local, "-")))
267                 fp = istore ? stdin : stdout;
268         else
269                 if ((fp = fopen(local, istore ? "r" : "w")) == NULL) {
270                         perror(local);
271                         goto bad;
272                 }
273
274         if (istore) {
275                 if (ascii) {
276                         int c;
277                         FILE *out;
278
279                         if (!(out = fdopen(data_fd, "w"))) {
280                                 perror("fdopen");
281                                 goto bad;
282                         }
283
284                         while ((c = getc(fp)) != EOF) {
285                                 if (c == '\n')
286                                         putc('\r', out);
287                                 if (putc(c, out) == EOF) {
288                                         perror("putc");
289                                         fclose(out);
290                                         data_fd = NOTOK;
291                                         goto bad;
292                                 }
293                         }
294
295                         fclose(out);
296                         data_fd = NOTOK;
297                 } else {
298                         while ((cc = fread(buffer, sizeof *buffer,
299                                         sizeof buffer, fp)) > 0)
300                                 if (write_tcp_socket(data_fd, buffer, cc)
301                                                 != cc) {
302                                         perror("write_tcp_socket");
303                                         goto bad;
304                                 }
305
306                         close_tcp_socket(data_fd), data_fd = NOTOK;
307                 }
308         } else {
309                 if (ascii) {
310                         int c;
311                         FILE *in;
312
313                         if (!(in = fdopen(data_fd, "r"))) {
314                                 perror("fdopen");
315                                 goto bad;
316                         }
317
318                         while ((c = getc(in)) != EOF) {
319                                 if (c == '\r')
320                                         switch (c = getc(in)) {
321                                                 case EOF:
322                                                 case '\0':
323                                                         c = '\r';
324                                                         break;
325
326                                                 case '\n':
327                                                         break;
328
329                                                 default:
330                                                         putc('\r', fp);
331                                                         break;
332                                                 }
333
334                                 if (putc(c, fp) == EOF) {
335                                         perror("putc");
336                                         fclose(in);
337                                         data_fd = NOTOK;
338                                         goto bad;
339                                 }
340                         }
341
342                         fclose(in);
343                         data_fd = NOTOK;
344                 } else {
345                         while ((cc = read_tcp_socket(data_fd, buffer,
346                                         sizeof buffer)) > 0)
347                                 if (fwrite(buffer, sizeof *buffer, cc, fp)
348                                                 == 0) {
349                                         perror("fwrite");
350                                         goto bad;
351                                 }
352                         if (cc < 0) {
353                                 perror("read_tcp_socket");
354                                 goto bad;
355                         }
356
357                         close_tcp_socket(data_fd), data_fd = NOTOK;
358                 }
359         }
360
361         if (!istdio)
362                 fclose(fp);
363
364         v_noise = v_verbose;
365         return (getreply(1, 0) == COMPLETE ? OK : NOTOK);
366 }
367
368
369 #define UC(b) (((int) b) & 0xff)
370
371 static int
372 initconn(void)
373 {
374         int len;
375         register char *a, *p;
376         struct sockaddr_in in_socket;
377
378         if (getsockname(ftp_fd, (struct sockaddr *) &in_socket,
379                 (len = sizeof(in_socket), &len)) == NOTOK) {
380                 perror("getsockname");
381                 return NOTOK;
382         }
383         in_socket.sin_port = 0;
384         if ((data_fd = start_tcp_server(&in_socket, 1, 0, 0)) == NOTOK) {
385                 perror("start_tcp_server");
386                 return NOTOK;
387         }
388
389         if (getsockname(data_fd, (struct sockaddr *) &in_socket,
390                 (len = sizeof in_socket, &len)) == NOTOK) {
391                 perror("getsockname");
392                 return NOTOK;
393         }
394
395         a = (char *) &in_socket.sin_addr;
396         p = (char *) &in_socket.sin_port;
397
398         if (command(1, "PORT %d,%d,%d,%d,%d,%d",
399                         UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
400                         UC(p[0]), UC(p[1])) == COMPLETE)
401                 return OK;
402
403         return NOTOK;
404 }
405
406 static int
407 dataconn(void)
408 {
409         int fd;
410         struct sockaddr_in in_socket;
411
412         if ((fd = join_tcp_client(data_fd, &in_socket)) == NOTOK) {
413                 perror("join_tcp_client");
414                 return NOTOK;
415         }
416         close_tcp_socket(data_fd);
417         data_fd = fd;
418
419         return OK;
420 }
421
422
423 static int
424 command(int arg1, ...)
425 {
426         int val;
427         va_list ap;
428
429         va_start(ap, arg1);
430         val = vcommand(arg1, ap);
431         va_end(ap);
432
433         return val;
434 }
435
436 static int
437 vcommand(int complete, va_list ap)
438 {
439         int len;
440         char buffer[BUFSIZ];
441
442         if (ftp_fd == NOTOK)
443                 return NOTOK;
444
445         _asnprintf(buffer, sizeof(buffer), NULL, ap);
446         if (v_debug)
447                 fprintf(stderr, "<--- %s\n", buffer);
448
449         strcat(buffer, "\r\n");
450         len = strlen(buffer);
451
452         if (write_tcp_socket(ftp_fd, buffer, len) != len) {
453                 perror("write_tcp_socket");
454                 return NOTOK;
455         }
456
457         return(getreply(complete, !strcmp(buffer, "QUIT")));
458 }
459
460
461 static int
462 getreply(int complete, int expecteof)
463 {
464         for (;;) {
465                 register int code, dig, n;
466                 int continuation;
467                 register char *bp;
468                 char buffer[BUFSIZ];
469
470                 code = dig = n = continuation = 0;
471                 bp = buffer;
472
473                 for (;;) {
474                         unsigned char c;
475
476                         if (read_tcp_socket(ftp_fd, &c, 1) < 1) {
477                                 if (expecteof)
478                                         return OK;
479
480                                 perror("read_tcp_socket");
481                                 return DONE;
482                         }
483                         if (c == '\n')
484                                 break;
485                         *bp++ = c != '\r' ? c : '\0';
486
487                         dig++;
488                         if (dig < 4) {
489                                 if (isdigit(c))
490                                         code = code * 10 + (c - '0');
491                                 else if (isspace(c))
492                                         /* XXX: naughty FTP... */
493                                         continuation++;
494                         } else
495                                 if (dig == 4 && c == '-')
496                                         continuation++;
497                         if (n == 0)
498                                 n = c;
499                 }
500
501                 if (v_debug)
502                         fprintf(stderr, "---> %s\n", buffer);
503                 if (continuation)
504                         continue;
505
506                 n -= '0';
507
508                 if (v_noise) {
509                         fprintf(stdout, "%s\n", buffer);
510                         fflush(stdout);
511                         v_noise = 0;
512                 } else
513                         if ((complete == -1 && n != PRELIM)
514                                 || (complete == 0 && n != CONTINUE && n != COMPLETE)
515                                 || (complete == 1 && n != COMPLETE))
516                                 fprintf(stderr, "%s\n", buffer);
517
518                 return n;
519         }
520 }