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