navigation
[oftp] / src / ftp.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdarg.h>
5 #include <errno.h>
6 #include <ctype.h>
7 #include <time.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <netinet/in.h>
11 #include <netdb.h>
12 #include "darray.h"
13 #include "ftp.h"
14 #include "util.h"
15
16 #ifdef __unix__
17 #include <unistd.h>
18 #include <fcntl.h>
19 #define closesocket             close
20 #define fcntlsocket             fcntl
21 #endif
22
23 #define TIMEOUT 15
24
25 static void free_dir(struct ftp_dirent *dir);
26
27 static int newconn(struct ftp *ftp);
28 static int sendcmd(struct ftp *ftp, const char *fmt, ...);
29 static int handle_control(struct ftp *ftp);
30 static int handle_data(struct ftp *ftp, int s);
31 static void proc_control(struct ftp *ftp, const char *buf);
32
33 static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls);
34 static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls);
35 static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls);
36
37 static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls);
38 static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls);
39
40
41 struct ftp *ftp_alloc(void)
42 {
43         struct ftp *ftp;
44
45         if(!(ftp = calloc(1, sizeof *ftp))) {
46                 return 0;
47         }
48         ftp->ctl = ftp->data = ftp->lis = -1;
49
50         ftp->dirent[0] = darr_alloc(0, sizeof *ftp->dirent[0]);
51         ftp->dirent[1] = darr_alloc(0, sizeof *ftp->dirent[1]);
52         return ftp;
53 }
54
55 void ftp_free(struct ftp *ftp)
56 {
57         if(!ftp) return;
58
59         if(ftp->ctl >= 0) {
60                 ftp_close(ftp);
61         }
62
63         free_dir(ftp->dirent[0]);
64         free_dir(ftp->dirent[1]);
65 }
66
67 static void free_dir(struct ftp_dirent *dir)
68 {
69         int i;
70         for(i=0; i<darr_size(dir); i++) {
71                 free(dir[i].name);
72         }
73         darr_free(dir);
74 }
75
76 int ftp_connect(struct ftp *ftp, const char *hostname, int port)
77 {
78         struct hostent *host;
79         struct sockaddr_in addr;
80
81         if((ftp->ctl = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
82                 errmsg("failed to create control socket\n");
83                 return -1;
84         }
85
86         if(!(host = gethostbyname(hostname))) {
87                 errmsg("failed to resolve host: %s\n", hostname);
88                 closesocket(ftp->ctl);
89                 ftp->ctl = -1;
90                 return -1;
91         }
92
93         memset(&addr, 0, sizeof addr);
94         addr.sin_family = AF_INET;
95         addr.sin_addr = *((struct in_addr*)host->h_addr);
96         addr.sin_port = htons(port);
97
98         if(connect(ftp->ctl, (struct sockaddr*)&addr, sizeof addr) == -1) {
99                 errmsg("failed to connect to: %s (port: %d)\n", hostname, port);
100                 closesocket(ftp->ctl);
101                 ftp->ctl = -1;
102                 return -1;
103         }
104
105         fcntlsocket(ftp->ctl, F_SETFL, fcntlsocket(ftp->ctl, F_GETFL) | O_NONBLOCK);
106         ftp->num_crecv = 0;
107         return 0;
108 }
109
110 void ftp_close(struct ftp *ftp)
111 {
112         if(ftp->ctl >= 0) {
113                 closesocket(ftp->ctl);
114                 ftp->ctl = -1;
115                 ftp->num_crecv = 0;
116         }
117         if(ftp->lis >= 0) {
118                 closesocket(ftp->lis);
119                 ftp->lis = -1;
120         }
121         if(ftp->data >= 0) {
122                 closesocket(ftp->data);
123                 ftp->data = -1;
124         }
125 }
126
127 int ftp_sockets(struct ftp *ftp, int *sockv, int maxsize)
128 {
129         int *sptr = sockv;
130         if(ftp->ctl >= 0 && maxsize-- > 0) {
131                 *sptr++ = ftp->ctl;
132         }
133         if(ftp->lis >= 0 && maxsize-- > 0) {
134                 *sptr++ = ftp->lis;
135         }
136         if(ftp->data >= 0 && maxsize-- > 0) {
137                 *sptr++ = ftp->data;
138         }
139         return sptr - sockv;
140 }
141
142 static void exec_op(struct ftp *ftp, int op, const char *arg)
143 {
144         switch(op) {
145         case FTP_PWD:
146                 ftp_pwd(ftp);
147                 break;
148
149         case FTP_CHDIR:
150                 ftp_chdir(ftp, arg);
151                 break;
152
153         case FTP_CDUP:
154                 ftp_chdir(ftp, "..");
155                 break;
156
157         case FTP_MKDIR:
158                 ftp_mkdir(ftp, arg);
159                 break;
160
161         case FTP_RMDIR:
162                 ftp_rmdir(ftp, arg);
163                 break;
164
165         case FTP_DEL:
166                 ftp_delete(ftp, arg);
167                 break;
168
169         case FTP_LIST:
170                 ftp_list(ftp);
171                 break;
172
173         case FTP_RETR:
174                 ftp_retrieve(ftp, arg);
175                 break;
176
177         case FTP_STORE:
178                 ftp_store(ftp, arg);
179                 break;
180
181         default:
182                 break;
183         }
184 }
185
186 static void exec_queued(struct ftp *ftp)
187 {
188         struct ftp_op *fop;
189
190         if(!(fop = ftp->qhead)) {
191                 return;
192         }
193
194         if(ftp->qtail == fop) {
195                 ftp->qhead = ftp->qtail = 0;
196         } else {
197                 ftp->qhead = ftp->qhead->next;
198         }
199
200         exec_op(ftp, fop->op, fop->arg);
201
202         free(fop->arg);
203         free(fop);
204 }
205
206
207 int ftp_queue(struct ftp *ftp, int op, const char *arg)
208 {
209         struct ftp_op *fop;
210
211         if(!ftp->busy && !ftp->qhead) {
212                 exec_op(ftp, op, arg);
213                 return 0;
214         }
215
216         if(!(fop = malloc(sizeof *fop))) {
217                 return -1;
218         }
219         if(arg) {
220                 if(!(fop->arg = strdup(arg))) {
221                         free(fop);
222                         return -1;
223                 }
224         } else {
225                 fop->arg = 0;
226         }
227         fop->op = op;
228         fop->next = 0;
229
230         if(ftp->qhead) {
231                 ftp->qtail->next = fop;
232                 ftp->qtail = fop;
233         } else {
234                 ftp->qhead = ftp->qtail = fop;
235         }
236         return 0;
237 }
238
239 int ftp_waitresp(struct ftp *ftp, time_t timeout)
240 {
241         fd_set rdset;
242         struct timeval tv;
243         time_t start;
244
245         ftp->last_resp = -1;
246         start = time(0);
247
248         for(;;) {
249                 FD_ZERO(&rdset);
250                 FD_SET(ftp->ctl, &rdset);
251
252                 if(timeout >= 0) {
253                         tv.tv_sec = timeout;
254                         tv.tv_usec = 0;
255                 }
256
257                 if(select(ftp->ctl + 1, &rdset, 0, 0, timeout >= 0 ? &tv : 0) == -1 && errno == EINTR) {
258                         continue;
259                 }
260
261                 if(FD_ISSET(ftp->ctl, &rdset)) {
262                         ftp->last_resp = -1;
263                         ftp_handle(ftp, ftp->ctl);
264                         if(ftp->last_resp) {
265                                 break;
266                         }
267                 }
268
269                 if(timeout > 0) {
270                         timeout -= time(0) - start;
271                         if(timeout <= 0) {
272                                 return -1;
273                         }
274                 }
275
276         }
277         return ftp->last_resp;
278 }
279
280 static int ftp_active(struct ftp *ftp)
281 {
282         struct sockaddr_in sa = {0};
283         socklen_t len;
284         unsigned long addr;
285         unsigned short port;
286
287         if(ftp->lis >= 0) {
288                 closesocket(ftp->lis);
289         }
290         if(ftp->data >= 0) {
291                 closesocket(ftp->data);
292                 ftp->data = -1;
293         }
294
295         if((ftp->lis = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
296                 errmsg("ftp_active: failed to create listening socket\n");
297                 return -1;
298         }
299
300         len = sizeof sa;
301         getsockname(ftp->ctl, (struct sockaddr*)&sa, &len);
302
303         sa.sin_family = AF_INET;
304         sa.sin_port = 0;
305
306         if(bind(ftp->lis, (struct sockaddr*)&sa, sizeof sa) == -1) {
307                 errmsg("ftp_active: failed to bind listening socket\n");
308                 closesocket(ftp->lis);
309                 ftp->lis = -1;
310                 return -1;
311         }
312         listen(ftp->lis, 1);
313
314         len = sizeof sa;
315         if(getsockname(ftp->lis, (struct sockaddr*)&sa, &len) == -1) {
316                 errmsg("ftp_active: failed to retrieve listening socket address\n");
317                 closesocket(ftp->lis);
318                 ftp->lis = -1;
319                 return -1;
320         }
321
322         addr = ntohl(sa.sin_addr.s_addr);
323         port = ntohs(sa.sin_port);
324         infomsg("listening on %s port %d\n", inet_ntoa(sa.sin_addr), port);
325
326         sendcmd(ftp, "PORT %d,%d,%d,%d,%d,%d", addr >> 24, (addr >> 16) & 0xff,
327                         (addr >> 8) & 0xff, addr & 0xff, port >> 8, port & 0xff);
328
329         ftp->cproc = cproc_active;
330         return 0;
331 }
332
333 int ftp_handle(struct ftp *ftp, int s)
334 {
335         if(s == ftp->ctl) {
336                 return handle_control(ftp);
337         }
338         if(s == ftp->data) {
339                 return handle_data(ftp, s);
340         }
341         if(s == ftp->lis) {
342                 int ns = accept(s, 0, 0);
343                 if(ftp->data >= 0) {
344                         closesocket(ns);
345                 } else {
346                         ftp->data = ns;
347                 }
348                 return 0;
349         }
350         return -1;
351 }
352
353 static int sendcmd(struct ftp *ftp, const char *fmt, ...)
354 {
355         char buf[256];
356         va_list ap;
357
358         if(ftp->ctl < 0) {
359                 return -1;
360         }
361
362         ftp->busy = 1;
363
364         va_start(ap, fmt);
365         vsprintf(buf, fmt, ap);
366         va_end(ap);
367         infomsg("send: %s\n", buf);
368         strcat(buf, "\r\n");
369         return send(ftp->ctl, buf, strlen(buf), 0);
370 }
371
372 static int handle_control(struct ftp *ftp)
373 {
374         int i, sz, rd;
375         char *buf, *start, *end;
376
377         for(;;) {
378                 if((sz = sizeof ftp->crecv - ftp->num_crecv) <= 0) {
379                         /* discard buffer */
380                         warnmsg("discard buffer\n");
381                         sz = sizeof ftp->crecv;
382                         ftp->num_crecv = 0;
383                 }
384                 start = ftp->crecv + ftp->num_crecv;
385                 if((rd = recv(ftp->ctl, start, sz, 0)) == -1) {
386                         if(errno == EINTR) continue;
387                         /* assume EWOULDBLOCK, try again next time */
388                         return 0;
389                 }
390                 if(rd == 0) {
391                         ftp_close(ftp);
392                         return -1;
393                 }
394
395                 end = start + rd;
396                 buf = ftp->crecv;
397                 for(i=0; i<rd; i++) {
398                         if(start[i] == '\r') {
399                                 start[i] = 0;
400                         } else if(start[i] == '\n') {
401                                 start[i] = 0;
402                                 proc_control(ftp, buf);
403                                 buf = start + i + 1;
404                         }
405                 }
406                 if(buf != ftp->crecv && buf < end) {
407                         ftp->num_crecv = end - buf;
408                         memmove(ftp->crecv, buf, ftp->num_crecv);
409                 } else {
410                         ftp->num_crecv = 0;
411                 }
412         }
413         return 0;
414 }
415
416 static int handle_data(struct ftp *ftp, int s)
417 {
418         int rd;
419
420         if(ftp->data == -1) {
421                 return -1;
422         }
423
424         for(;;) {
425                 if((rd = recv(ftp->data, ftp->drecv, sizeof ftp->drecv, 0)) == -1) {
426                         if(errno == EINTR) continue;
427                         /* assume EWOULDBLOCK, try again next time */
428                         break;
429                 }
430
431                 /* call the callback first, so that we'll get a 0-count call to indicate
432                  * EOF when the server closes the data connection
433                  */
434                 if(ftp->dproc) {
435                         ftp->dproc(ftp, ftp->drecv, rd, ftp->dproc_cls);
436                 }
437
438                 if(rd == 0) {
439                         closesocket(ftp->data);
440                         ftp->data = -1;
441                         return -1;
442                 }
443         }
444         return 0;
445 }
446
447 static int respcode(const char *resp)
448 {
449         if(!isdigit(resp[0]) || !isdigit(resp[1]) || !isdigit(resp[2])) {
450                 return 0;
451         }
452
453         if(isspace(resp[3])) {
454                 return atoi(resp);
455         }
456         if(resp[3] == '-') {
457                 return -atoi(resp);
458         }
459         return 0;
460 }
461
462 static void proc_control(struct ftp *ftp, const char *buf)
463 {
464         int code;
465         char *end;
466
467         while(*buf && isspace(*buf)) buf++;
468         if((end = strchr(buf, '\r'))) {
469                 *end = 0;
470         }
471
472         infomsg("recv: %s\n", buf);
473
474         if((code = respcode(buf)) == 0) {
475                 warnmsg("ignoring invalid response: %s\n", buf);
476                 return;
477         }
478         if(code < 0) {
479                 return; /* ignore continuations for now */
480         }
481
482         ftp->last_resp = code;
483
484         if(ftp->cproc) {
485                 if(ftp->cproc(ftp, code, buf, ftp->cproc_cls) <= 0) {
486                         ftp->cproc = 0;
487                         ftp->busy = 0;
488
489                         /* execute next operation if there's one queued */
490                         exec_queued(ftp);
491                 }
492                 return;
493         }
494         ftp->busy = 0;
495
496         switch(code) {
497         case 220:
498                 sendcmd(ftp, "user %s", ftp->user ? ftp->user : "anonymous");
499                 break;
500         case 331:
501                 sendcmd(ftp, "pass %s", ftp->pass ? ftp->pass : "foobar");
502                 break;
503         case 230:
504                 infomsg("login successful\n");
505                 if(newconn(ftp) == -1) {
506                         ftp_close(ftp);
507                 }
508                 break;
509         case 530:
510                 ftp->status = 0;
511                 errmsg("login failed\n");
512                 break;
513         }
514 }
515
516 static int newconn(struct ftp *ftp)
517 {
518         if(ftp_active(ftp) == -1) {
519                 return -1;
520         }
521         ftp_queue(ftp, FTP_PWD, 0);
522         ftp_queue(ftp, FTP_LIST, 0);
523         return 0;
524 }
525
526 int ftp_update(struct ftp *ftp)
527 {
528         return -1;
529 }
530
531 int ftp_pwd(struct ftp *ftp)
532 {
533         sendcmd(ftp, "pwd");
534         ftp->cproc = cproc_pwd;
535         return 0;
536 }
537
538 int ftp_chdir(struct ftp *ftp, const char *dirname)
539 {
540         if(strcmp(dirname, "..") == 0) {
541                 sendcmd(ftp, "cdup");
542         } else {
543                 sendcmd(ftp, "cwd %s", dirname);
544         }
545         ftp->cproc = cproc_cwd;
546         return 0;
547 }
548
549 int ftp_mkdir(struct ftp *ftp, const char *dirname)
550 {
551         return -1;
552 }
553
554 int ftp_rmdir(struct ftp *ftp, const char *dirname)
555 {
556         return -1;
557 }
558
559 int ftp_delete(struct ftp *ftp, const char *fname)
560 {
561         return -1;
562 }
563
564 struct recvbuf {
565         char *buf;
566         long size, bufsz;
567 };
568
569 int ftp_list(struct ftp *ftp)
570 {
571         struct recvbuf *rbuf;
572
573         if(!(rbuf = malloc(sizeof *rbuf))) {
574                 errmsg("failed to allocate receive buffer\n");
575                 return -1;
576         }
577         rbuf->size = 0;
578         rbuf->bufsz = 1024;
579         if(!(rbuf->buf = malloc(rbuf->bufsz))) {
580                 free(rbuf);
581                 errmsg("failed to allocate receive buffer\n");
582                 return -1;
583         }
584
585         sendcmd(ftp, "list");
586         ftp->cproc = cproc_list;
587         ftp->dproc = dproc_list;
588         ftp->cproc_cls = ftp->dproc_cls = rbuf;
589         return 0;
590 }
591
592 int ftp_retrieve(struct ftp *ftp, const char *fname)
593 {
594         return -1;
595 }
596
597 int ftp_store(struct ftp *ftp, const char *fname)
598 {
599         return -1;
600 }
601
602 static int get_quoted_text(const char *str, char *buf)
603 {
604         int len;
605         const char *src, *end;
606
607         if(!(src = strchr(str, '"'))) {
608                 return -1;
609         }
610         src++;
611         end = src;
612         while(*end && *end != '"') end++;
613         if(!*end) return -1;
614
615         len = end - src;
616         memcpy(buf, src, len);
617         buf[len] = 0;
618         return 0;
619 }
620
621 static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls)
622 {
623         if(code != 200) {
624                 errmsg("ftp_active failed\n");
625                 ftp_close(ftp);
626         } else {
627                 ftp->status = FTP_CONN_ACT;
628         }
629         return 0;
630 }
631
632 static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
633 {
634         char *dirname;
635
636         if(code != 257) {
637                 warnmsg("pwd failed\n");
638                 return -1;
639         }
640
641         dirname = alloca(strlen(buf) + 1);
642         if(get_quoted_text(buf, dirname) == -1) {
643                 warnmsg("pwd: invalid response: %s\n", buf);
644                 return -1;
645         }
646
647         free(ftp->curdir[FTP_REMOTE]);
648         ftp->curdir[FTP_REMOTE] = strdup_nf(dirname);
649         ftp->modified = FTP_MOD_REMDIR;
650         return 0;
651 }
652
653 static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
654 {
655         if(code != 250) {
656                 warnmsg("cwd failed\n");
657                 return -1;
658         }
659
660         ftp_queue(ftp, FTP_PWD, 0);
661         ftp_queue(ftp, FTP_LIST, 0);
662         return 0;
663 }
664
665 static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls)
666 {
667         if(code < 200) {
668                 /* expect more */
669                 return 1;
670         }
671
672         if(code >= 400) {
673                 errmsg("failed to retrieve directory listing\n");
674         }
675         return 0;
676 }
677
678 #define SKIP_FIELD(p) \
679         do { \
680                 while(*(p) && *(p) != '\n' && !isspace(*(p))) (p)++; \
681                 while(*(p) && *(p) != '\n' && isspace(*(p))) (p)++; \
682         } while(0)
683
684 static int parse_dirent(struct ftp_dirent *ent, const char *line)
685 {
686         int len;
687         const char *ptr = line;
688         const char *end;
689
690         if(!(end = strchr(line, '\r')) && !(end = strchr(line, '\n'))) {
691                 return -1;
692         }
693
694         if(line[0] == 'd') {
695                 ent->type = FTP_DIR;
696         } else {
697                 ent->type = FTP_FILE;
698         }
699
700         SKIP_FIELD(ptr);                /* skip mode */
701         SKIP_FIELD(ptr);                /* skip links */
702         SKIP_FIELD(ptr);                /* skip owner */
703         SKIP_FIELD(ptr);                /* skip group */
704
705         if(ent->type == FTP_FILE) {
706                 ent->size = atoi(ptr);
707         }
708         SKIP_FIELD(ptr);                /* skip size */
709         SKIP_FIELD(ptr);                /* skip month */
710         SKIP_FIELD(ptr);                /* skip day */
711         SKIP_FIELD(ptr);                /* skip year */
712
713         if(ptr >= end) return -1;
714
715         len = end - ptr;
716         ent->name = malloc(len + 1);
717         memcpy(ent->name, ptr, len);
718         ent->name[len] = 0;
719
720         return 0;
721 }
722
723 static int direntcmp(const void *a, const void *b)
724 {
725         const struct ftp_dirent *da = a, *db = b;
726
727         if(da->type == db->type) {
728                 return strcmp(da->name, db->name);
729         }
730         return da->type == FTP_DIR ? -1 : 1;
731 }
732
733 static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls)
734 {
735         int num;
736         struct recvbuf *rbuf = cls;
737
738         if(sz == 0) {
739                 /* EOF condition, we got the whole list, update directory entries */
740                 char *ptr = rbuf->buf;
741                 char *end = rbuf->buf + rbuf->size;
742                 struct ftp_dirent ent;
743
744                 darr_clear(ftp->dirent[FTP_REMOTE]);
745
746                 while(ptr < end) {
747                         if(parse_dirent(&ent, ptr) != -1) {
748                                 darr_push(ftp->dirent[FTP_REMOTE], &ent);
749                         }
750                         while(ptr < end && *ptr != '\n' && *ptr != '\r') ptr++;
751                         while(ptr < end && (*ptr == '\r' || *ptr == '\n')) ptr++;
752                 }
753                 ftp->modified |= FTP_MOD_REMDIR;
754
755                 free(rbuf->buf);
756                 free(rbuf);
757                 ftp->dproc = 0;
758
759                 num = darr_size(ftp->dirent[FTP_REMOTE]);
760                 qsort(ftp->dirent[FTP_REMOTE], num, sizeof *ftp->dirent[FTP_REMOTE], direntcmp);
761                 return;
762         }
763
764         if(rbuf->size + sz > rbuf->bufsz) {
765                 char *tmp;
766                 int newsz = rbuf->bufsz << 1;
767
768                 if(!(tmp = realloc(rbuf->buf, newsz))) {
769                         errmsg("failed to resize receive buffer\n");
770                         return;
771                 }
772                 rbuf->buf = tmp;
773                 rbuf->bufsz = newsz;
774         }
775
776         memcpy(rbuf->buf + rbuf->size, buf, sz);
777         rbuf->size += sz;
778 }
779
780 const char *ftp_curdir(struct ftp *ftp, int whichdir)
781 {
782         return ftp->curdir[whichdir];
783 }
784
785 int ftp_num_dirent(struct ftp *ftp, int whichdir)
786 {
787         return darr_size(ftp->dirent[whichdir]);
788 }
789
790 struct ftp_dirent *ftp_dirent(struct ftp *ftp, int whichdir, int idx)
791 {
792         return ftp->dirent[whichdir] + idx;
793 }