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