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