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