Remove HAVE_ARPA_FTP_H and arpa/ftp.h references.
[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 notdef_lyndon_posix
13 /*
14  * Does this get included indirectly?  Let's find out.  It's not
15  * part of Posix.  Mail lyndon@orthanc.ca if this breaks your build.
16  */
17 # include <arpa/ftp.h>
18 #endif
19
20 #define v_debug debugsw
21 #define v_verbose verbosw
22
23 static int ftp_fd = NOTOK;
24 static int data_fd = NOTOK;
25
26 static int v_noise;
27
28 extern int v_debug;
29 extern int v_verbose;
30
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <netdb.h>
35 #include <errno.h>
36
37 #define start_tcp_client(res) \
38         socket (res->ai_family, res->ai_socktype, res->ai_protocol)
39
40 #define join_tcp_server(fd, sock, len) \
41         connect ((fd), (struct sockaddr *) (sock), len)
42
43 /*
44  * prototypes
45  */
46 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
47 int ftp_trans (char *, char *, char *, char *, char *, char *, char *, int, int);
48
49 /*
50  * static prototypes
51  */
52 static int start_tcp_server (struct sockaddr_in *, int, int, int);
53 static void _asnprintf (char *, int, char *, va_list);
54 static int ftp_quit (void);
55 static int ftp_read (char *, char *, char *, int);
56 static int initconn (void);
57 static int dataconn (void);
58 static int command (int arg1, ...);
59 static int vcommand (int, va_list);
60 static int getreply (int, int);
61
62
63 static int
64 start_tcp_server (struct sockaddr_in *sock, int backlog, int opt1, int opt2)
65 {
66     int eindex, sd;
67
68     if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == NOTOK)
69         return NOTOK;
70
71     if (bind (sd, (struct sockaddr *) sock, sizeof *sock) == NOTOK) {
72         eindex = errno;
73         close (sd);
74         errno = eindex;
75     } else {
76         listen (sd, backlog);
77     }
78
79     return sd;
80 }
81
82
83 static int __len__;
84
85 #define join_tcp_client(fd,sock) \
86         accept ((fd), (struct sockaddr *) (sock), \
87                 (__len__ = sizeof *(sock), &__len__))
88
89 #define read_tcp_socket  read
90 #define write_tcp_socket write
91 #define close_tcp_socket close
92
93 static void
94 _asnprintf (char *bp, int len_bp, char *what, va_list ap)
95 {
96     int eindex, len;
97     char *fmt;
98
99     eindex = errno;
100
101     *bp = '\0';
102     fmt = va_arg (ap, char *);
103
104     if (fmt) {
105         vsnprintf(bp, len_bp, fmt, ap);
106         len = strlen(bp);
107         bp += len;
108         len_bp -= len;
109     }
110
111     if (what) {
112         char *s;
113
114         if (*what) {
115             snprintf (bp, len_bp, " %s: ", what);
116             len = strlen (bp);
117             bp += len;
118             len_bp -= len;
119         }
120         if ((s = strerror(eindex)))
121             strncpy (bp, s, len_bp);
122         else
123             snprintf (bp, len_bp, "Error %d", eindex);
124         bp += strlen (bp);
125     }
126
127     errno = eindex;
128 }
129
130
131 int
132 ftp_get (char *host, char *user, char *password, char *cwd,
133          char *remote, char *local, int ascii, int stayopen)
134 {
135     return ftp_trans (host, user, password, cwd, remote, local,
136                       "RETR", ascii, stayopen);
137 }
138
139
140 int
141 ftp_trans (char *host, char *user, char *password, char *cwd, char *remote,
142            char *local, char *cmd, int ascii, int stayopen)
143 {
144     int result;
145
146     if (stayopen <= 0) {
147         result = ftp_quit ();
148         if (host == NULL)
149             return result;
150     }
151
152     if (ftp_fd == NOTOK) {
153         struct addrinfo hints, *res;
154
155         memset(&hints, 0, sizeof(hints));
156 #ifdef AI_ADDRCONFIG
157         hints.ai_flags = AI_ADDRCONFIG;
158 #endif
159         hints.ai_family = PF_INET;
160         hints.ai_socktype = SOCK_STREAM;
161
162         result = getaddrinfo(host, "ftp", &hints, &res);
163
164         if (result) {
165             fprintf(stderr, "%s/ftp: %s\n", host, gai_strerror(result));
166             return NOTOK;
167         }
168
169         if ((ftp_fd = start_tcp_client (res)) == NOTOK) {
170             perror (host);
171             freeaddrinfo(res);
172             return NOTOK;
173         }
174         if (join_tcp_server (ftp_fd, res->ai_addr, res->ai_addrlen) == NOTOK) {
175             perror (host);
176             freeaddrinfo(res);
177             close_tcp_socket (ftp_fd), ftp_fd = NOTOK;
178             return NOTOK;
179         }
180         freeaddrinfo(res);
181         getreply (1, 0);
182
183         if (v_verbose) {
184             fprintf (stdout, "Connected to %s\n", host);
185             fflush (stdout);
186         }
187
188         if (user) {
189             if ((result = command (0, "USER %s", user)) == 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         }
298         else {
299             while ((cc = fread (buffer, sizeof *buffer, sizeof buffer, fp)) > 0)
300                 if (write_tcp_socket (data_fd, buffer, cc) != cc) {
301                     perror ("write_tcp_socket");
302                     goto bad;
303                 }
304
305             close_tcp_socket (data_fd), data_fd = NOTOK;
306         }
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         }
345         else {
346             while ((cc = read_tcp_socket (data_fd, buffer, sizeof buffer)) > 0)
347                 if (fwrite (buffer, sizeof *buffer, cc, fp) == 0) {
348                     perror ("fwrite");
349                     goto bad;
350                 }
351             if (cc < 0) {
352                 perror ("read_tcp_socket");
353                 goto bad;
354             }
355
356             close_tcp_socket (data_fd), data_fd = NOTOK;
357         }
358     }
359
360     if (!istdio)
361         fclose (fp);
362
363     v_noise = v_verbose;
364     return (getreply (1, 0) == COMPLETE ? OK : NOTOK);
365 }
366
367
368 #define UC(b) (((int) b) & 0xff)
369
370 static int
371 initconn (void)
372 {
373     int len;
374     register char *a, *p;
375     struct sockaddr_in in_socket;
376
377     if (getsockname (ftp_fd, (struct sockaddr *) &in_socket,
378                      (len = sizeof(in_socket), &len)) == NOTOK) {
379         perror ("getsockname");
380         return NOTOK;
381     }
382     in_socket.sin_port = 0;
383     if ((data_fd = start_tcp_server (&in_socket, 1, 0, 0)) == NOTOK) {
384         perror ("start_tcp_server");
385         return NOTOK;
386     }
387
388     if (getsockname (data_fd, (struct sockaddr *) &in_socket,
389                      (len = sizeof in_socket, &len)) == NOTOK) {
390         perror ("getsockname");
391         return NOTOK;
392     }
393
394     a = (char *) &in_socket.sin_addr;
395     p = (char *) &in_socket.sin_port;
396
397     if (command (1, "PORT %d,%d,%d,%d,%d,%d",
398                       UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
399                       UC(p[0]), UC(p[1])) == COMPLETE)
400         return OK;
401
402     return NOTOK;
403 }
404
405 static int
406 dataconn (void)
407 {
408     int fd;
409     struct sockaddr_in in_socket;
410     
411     if ((fd = join_tcp_client (data_fd, &in_socket)) == NOTOK) {
412         perror ("join_tcp_client");
413         return NOTOK;
414     }
415     close_tcp_socket (data_fd);
416     data_fd = fd;
417
418     return OK;
419 }
420
421
422 static int
423 command (int arg1, ...)
424 {
425     int val;
426     va_list ap;
427
428     va_start (ap, arg1);
429     val = vcommand (arg1, ap);
430     va_end (ap);
431
432     return val;
433 }
434
435 static int
436 vcommand (int complete, va_list ap)
437 {
438     int len;
439     char buffer[BUFSIZ];
440
441     if (ftp_fd == NOTOK)
442         return NOTOK;
443
444     _asnprintf (buffer, sizeof(buffer), NULL, ap);
445     if (v_debug)
446         fprintf (stderr, "<--- %s\n", buffer);
447
448     strcat (buffer, "\r\n");
449     len = strlen (buffer);
450
451     if (write_tcp_socket (ftp_fd, buffer, len) != len) {
452         perror ("write_tcp_socket");
453         return NOTOK;
454     }
455
456     return (getreply (complete, !strcmp (buffer, "QUIT")));
457 }
458
459
460 static int
461 getreply (int complete, int expecteof)
462 {
463     for (;;) {
464         register int code, dig, n;
465         int continuation;
466         register char *bp;
467         char buffer[BUFSIZ];
468
469         code = dig = n = continuation = 0;
470         bp = buffer;
471
472         for (;;) {
473             unsigned char c;
474
475             if (read_tcp_socket (ftp_fd, &c, 1) < 1) {
476                 if (expecteof)
477                     return OK;
478
479                 perror ("read_tcp_socket");
480                 return DONE;
481             }
482             if (c == '\n')
483                 break;
484             *bp++ = c != '\r' ? c : '\0';
485
486             dig++;
487             if (dig < 4) {
488                 if (isdigit(c))
489                     code = code * 10 + (c - '0');
490                 else                            /* XXX: naughty FTP... */
491                     if (isspace (c))
492                         continuation++;
493             }
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         }
513         else
514             if ((complete == -1 && n != PRELIM)
515                     || (complete == 0 && n != CONTINUE && n != COMPLETE)
516                     || (complete == 1 && n != COMPLETE))
517                 fprintf (stderr, "%s\n", buffer);
518
519         return n;
520     }
521 }