download, progress, improved screen updates...
[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 <sys/select.h>
10 #include <sys/swap.h>
11 #include <arpa/inet.h>
12 #include <netinet/in.h>
13 #include <netdb.h>
14 #include "darray.h"
15 #include "ftp.h"
16 #include "util.h"
17
18 #ifdef __unix__
19 #include <unistd.h>
20 #include <fcntl.h>
21 #define closesocket             close
22 #define fcntlsocket             fcntl
23 #else
24 #define select  select_s
25
26 #ifndef O_NONBLOCK
27 #define O_NONBLOCK      0x2000
28 #endif
29 #ifndef F_SETFL
30 #define F_GETFL 101
31 #define F_SETFL 102
32 #endif
33 #endif
34
35 #define TIMEOUT 15
36
37 static void free_dir(struct ftp_dirent *dir);
38
39 static int newconn(struct ftp *ftp);
40 static int sendcmd(struct ftp *ftp, const char *fmt, ...);
41 static int handle_control(struct ftp *ftp);
42 static int handle_data(struct ftp *ftp, int s);
43 static void proc_control(struct ftp *ftp, const char *buf);
44
45 static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls);
46 static int cproc_pasv(struct ftp *ftp, int code, const char *buf, void *cls);
47 static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls);
48 static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls);
49
50 static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls);
51 static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls);
52
53 static int cproc_xfer(struct ftp *ftp, int code, const char *buf, void *cls);
54 static void dproc_xfer(struct ftp *ftp, const char *buf, int sz, void *cls);
55
56
57 struct ftp *ftp_alloc(void)
58 {
59         struct ftp *ftp;
60
61         if(!(ftp = calloc(1, sizeof *ftp))) {
62                 return 0;
63         }
64         ftp->ctl = ftp->data = ftp->lis = -1;
65         ftp->passive = 1;
66
67         ftp->dirent = darr_alloc(0, sizeof *ftp->dirent);
68         return ftp;
69 }
70
71 void ftp_free(struct ftp *ftp)
72 {
73         if(!ftp) return;
74
75         if(ftp->ctl >= 0) {
76                 ftp_close(ftp);
77         }
78
79         free_dir(ftp->dirent);
80 }
81
82 static void free_dir(struct ftp_dirent *dir)
83 {
84         int i;
85         for(i=0; i<darr_size(dir); i++) {
86                 free(dir[i].name);
87         }
88         darr_free(dir);
89 }
90
91 int ftp_connect(struct ftp *ftp, const char *hostname, int port)
92 {
93         struct hostent *host;
94         struct sockaddr_in addr;
95
96         if((ftp->ctl = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
97                 errmsg("failed to create control socket\n");
98                 return -1;
99         }
100
101         if(!(host = gethostbyname(hostname))) {
102                 errmsg("failed to resolve host: %s\n", hostname);
103                 closesocket(ftp->ctl);
104                 ftp->ctl = -1;
105                 return -1;
106         }
107
108         memset(&addr, 0, sizeof addr);
109         addr.sin_family = AF_INET;
110         addr.sin_addr = *((struct in_addr*)host->h_addr);
111         addr.sin_port = htons(port);
112
113         if(connect(ftp->ctl, (struct sockaddr*)&addr, sizeof addr) == -1) {
114                 errmsg("failed to connect to: %s (port: %d)\n", hostname, port);
115                 closesocket(ftp->ctl);
116                 ftp->ctl = -1;
117                 return -1;
118         }
119
120         fcntlsocket(ftp->ctl, F_SETFL, fcntlsocket(ftp->ctl, F_GETFL) | O_NONBLOCK);
121         ftp->num_crecv = 0;
122         return 0;
123 }
124
125 void ftp_close(struct ftp *ftp)
126 {
127         if(ftp->ctl >= 0) {
128                 closesocket(ftp->ctl);
129                 ftp->ctl = -1;
130                 ftp->num_crecv = 0;
131         }
132         if(ftp->lis >= 0) {
133                 closesocket(ftp->lis);
134                 ftp->lis = -1;
135         }
136         if(ftp->data >= 0) {
137                 closesocket(ftp->data);
138                 ftp->data = -1;
139         }
140 }
141
142 int ftp_sockets(struct ftp *ftp, int *sockv, int maxsize)
143 {
144         int *sptr = sockv;
145         if(ftp->ctl >= 0 && maxsize-- > 0) {
146                 *sptr++ = ftp->ctl;
147         }
148         if(ftp->lis >= 0 && maxsize-- > 0) {
149                 *sptr++ = ftp->lis;
150         }
151         if(ftp->data >= 0 && maxsize-- > 0) {
152                 *sptr++ = ftp->data;
153         }
154         return sptr - sockv;
155 }
156
157 static void exec_op(struct ftp *ftp, struct ftp_op *fop)
158 {
159         switch(fop->op) {
160         case FTP_TYPE:
161                 ftp_type(ftp, fop->arg);
162                 break;
163
164         case FTP_PWD:
165                 ftp_pwd(ftp);
166                 break;
167
168         case FTP_CHDIR:
169                 ftp_chdir(ftp, fop->arg);
170                 break;
171
172         case FTP_CDUP:
173                 ftp_chdir(ftp, "..");
174                 break;
175
176         case FTP_MKDIR:
177                 ftp_mkdir(ftp, fop->arg);
178                 break;
179
180         case FTP_RMDIR:
181                 ftp_rmdir(ftp, fop->arg);
182                 break;
183
184         case FTP_DEL:
185                 ftp_delete(ftp, fop->arg);
186                 break;
187
188         case FTP_LIST:
189                 ftp_list(ftp);
190                 break;
191
192         case FTP_RETR:
193                 ftp_retrieve(ftp, fop->arg);
194                 break;
195
196         case FTP_STORE:
197                 ftp_store(ftp, fop->arg);
198                 break;
199
200         case FTP_XFER:
201                 ftp_transfer(ftp, fop->data);
202                 break;
203
204         default:
205                 break;
206         }
207 }
208
209 static void exec_queued(struct ftp *ftp)
210 {
211         struct ftp_op *fop;
212
213         if(!(fop = ftp->qhead)) {
214                 return;
215         }
216
217         if(ftp->qtail == fop) {
218                 ftp->qhead = ftp->qtail = 0;
219         } else {
220                 ftp->qhead = ftp->qhead->next;
221         }
222
223         exec_op(ftp, fop);
224
225         free(fop->arg);
226         free(fop);
227 }
228
229
230 int ftp_queue(struct ftp *ftp, int op, const char *arg)
231 {
232         struct ftp_op *fop;
233
234         if(!ftp->busy && !ftp->qhead) {
235                 struct ftp_op tmp = {0};
236                 tmp.op = op;
237                 tmp.arg = (char*)arg;
238                 exec_op(ftp, &tmp);
239                 return 0;
240         }
241
242         if(!(fop = malloc(sizeof *fop))) {
243                 return -1;
244         }
245         if(arg) {
246                 if(!(fop->arg = strdup(arg))) {
247                         free(fop);
248                         return -1;
249                 }
250         } else {
251                 fop->arg = 0;
252         }
253         fop->op = op;
254         fop->next = 0;
255
256         if(ftp->qhead) {
257                 ftp->qtail->next = fop;
258                 ftp->qtail = fop;
259         } else {
260                 ftp->qhead = ftp->qtail = fop;
261         }
262         return 0;
263 }
264
265 int ftp_queue_transfer(struct ftp *ftp, struct ftp_transfer *xfer)
266 {
267         struct ftp_op *fop;
268
269         if(!ftp->busy && !ftp->qhead) {
270                 struct ftp_op tmp = {0};
271                 tmp.op = FTP_XFER;
272                 tmp.data = xfer;
273                 exec_op(ftp, &tmp);
274                 return 0;
275         }
276
277         if(!(fop = malloc(sizeof *fop))) {
278                 return -1;
279         }
280         fop->op = FTP_XFER;
281         fop->arg = 0;
282         fop->data = xfer;
283         fop->next = 0;
284
285         if(ftp->qhead) {
286                 ftp->qtail->next = fop;
287                 ftp->qtail = fop;
288         } else {
289                 ftp->qhead = ftp->qtail = fop;
290         }
291         return 0;
292 }
293
294 int ftp_waitresp(struct ftp *ftp, long timeout)
295 {
296         fd_set rdset;
297         struct timeval tv;
298         time_t start;
299
300         ftp->last_resp = -1;
301         start = time(0);
302
303         for(;;) {
304                 FD_ZERO(&rdset);
305                 FD_SET(ftp->ctl, &rdset);
306
307                 if(timeout >= 0) {
308                         tv.tv_sec = timeout;
309                         tv.tv_usec = 0;
310                 }
311
312                 if(select(ftp->ctl + 1, &rdset, 0, 0, timeout >= 0 ? &tv : 0) == -1 && errno == EINTR) {
313                         continue;
314                 }
315
316                 if(FD_ISSET(ftp->ctl, &rdset)) {
317                         ftp->last_resp = -1;
318                         ftp_handle(ftp, ftp->ctl);
319                         if(ftp->last_resp) {
320                                 break;
321                         }
322                 }
323
324                 if(timeout > 0) {
325                         timeout -= time(0) - start;
326                         if(timeout <= 0) {
327                                 return -1;
328                         }
329                 }
330
331         }
332         return ftp->last_resp;
333 }
334
335 static int ftp_active(struct ftp *ftp)
336 {
337         struct sockaddr_in sa = {0};
338         int len;
339         unsigned long addr;
340         unsigned short port;
341
342         if(ftp->lis >= 0) {
343                 closesocket(ftp->lis);
344         }
345         if(ftp->data >= 0) {
346                 closesocket(ftp->data);
347                 ftp->data = -1;
348         }
349
350         if((ftp->lis = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
351                 errmsg("ftp_active: failed to create listening socket\n");
352                 return -1;
353         }
354
355         len = sizeof sa;
356         getsockname(ftp->ctl, (struct sockaddr*)&sa, &len);
357
358         sa.sin_family = AF_INET;
359         sa.sin_port = 0;
360
361         if(bind(ftp->lis, (struct sockaddr*)&sa, sizeof sa) == -1) {
362                 errmsg("ftp_active: failed to bind listening socket\n");
363                 closesocket(ftp->lis);
364                 ftp->lis = -1;
365                 return -1;
366         }
367         listen(ftp->lis, 1);
368
369         len = sizeof sa;
370         if(getsockname(ftp->lis, (struct sockaddr*)&sa, &len) == -1) {
371                 errmsg("ftp_active: failed to retrieve listening socket address\n");
372                 closesocket(ftp->lis);
373                 ftp->lis = -1;
374                 return -1;
375         }
376
377         addr = ntohl(sa.sin_addr.s_addr);
378         port = ntohs(sa.sin_port);
379         infomsg("listening on %s port %d\n", inet_ntoa(sa.sin_addr), port);
380
381         sendcmd(ftp, "PORT %d,%d,%d,%d,%d,%d", addr >> 24, (addr >> 16) & 0xff,
382                         (addr >> 8) & 0xff, addr & 0xff, port >> 8, port & 0xff);
383
384         ftp->cproc = cproc_active;
385         return 0;
386 }
387
388 static int ftp_passive(struct ftp *ftp)
389 {
390         if(ftp->lis >= 0) {
391                 closesocket(ftp->lis);
392                 ftp->lis = -1;
393         }
394         if(ftp->data >= 0) {
395                 closesocket(ftp->data);
396                 ftp->data = -1;
397         }
398
399         sendcmd(ftp, "PASV");
400         ftp->cproc = cproc_pasv;
401         return 0;
402 }
403
404 int ftp_handle(struct ftp *ftp, int s)
405 {
406         if(s == ftp->ctl) {
407                 return handle_control(ftp);
408         }
409         if(s == ftp->data) {
410                 return handle_data(ftp, s);
411         }
412         if(s == ftp->lis) {
413                 int ns = accept(s, 0, 0);
414                 if(ftp->data >= 0) {
415                         closesocket(ns);
416                 } else {
417                         ftp->data = ns;
418                 }
419                 return 0;
420         }
421         return -1;
422 }
423
424 static int sendcmd(struct ftp *ftp, const char *fmt, ...)
425 {
426         char buf[256];
427         va_list ap;
428
429         if(ftp->ctl < 0) {
430                 return -1;
431         }
432
433         ftp->busy = 1;
434
435         va_start(ap, fmt);
436         vsprintf(buf, fmt, ap);
437         va_end(ap);
438         infomsg("send: %s\n", buf);
439         strcat(buf, "\r\n");
440         return send(ftp->ctl, buf, strlen(buf), 0);
441 }
442
443 static int handle_control(struct ftp *ftp)
444 {
445         int i, sz, rd;
446         char *buf, *start, *end;
447
448         for(;;) {
449                 if((sz = sizeof ftp->crecv - ftp->num_crecv) <= 0) {
450                         /* discard buffer */
451                         warnmsg("discard buffer\n");
452                         sz = sizeof ftp->crecv;
453                         ftp->num_crecv = 0;
454                 }
455                 start = ftp->crecv + ftp->num_crecv;
456                 if((rd = recv(ftp->ctl, start, sz, 0)) == -1) {
457                         if(errno == EINTR) continue;
458                         /* assume EWOULDBLOCK, try again next time */
459                         return 0;
460                 }
461                 if(rd == 0) {
462                         ftp_close(ftp);
463                         return -1;
464                 }
465
466                 end = start + rd;
467                 buf = ftp->crecv;
468                 for(i=0; i<rd; i++) {
469                         if(start[i] == '\r') {
470                                 start[i] = 0;
471                         } else if(start[i] == '\n') {
472                                 start[i] = 0;
473                                 proc_control(ftp, buf);
474                                 buf = start + i + 1;
475                         }
476                 }
477                 if(buf != ftp->crecv && buf < end) {
478                         ftp->num_crecv = end - buf;
479                         memmove(ftp->crecv, buf, ftp->num_crecv);
480                 } else {
481                         ftp->num_crecv = 0;
482                 }
483         }
484         return 0;
485 }
486
487 static int handle_data(struct ftp *ftp, int s)
488 {
489         int i, rd;
490
491         if(ftp->data == -1) {
492                 return -1;
493         }
494
495         /* get at most 4 packets at a time, to allow returning back to the main loop
496          * to process input
497          */
498         for(i=0; i<4; i++) {
499                 if((rd = recv(ftp->data, ftp->drecv, sizeof ftp->drecv, 0)) == -1) {
500                         if(errno == EINTR) continue;
501                         /* assume EWOULDBLOCK, try again next time */
502                         break;
503                 }
504
505                 /* call the callback first, so that we'll get a 0-count call to indicate
506                  * EOF when the server closes the data connection
507                  */
508                 if(ftp->dproc) {
509                         ftp->dproc(ftp, ftp->drecv, rd, ftp->dproc_cls);
510                 }
511
512                 if(rd == 0) {
513                         closesocket(ftp->data);
514                         ftp->data = -1;
515                         return -1;
516                 }
517         }
518         return 0;
519 }
520
521 static int respcode(const char *resp)
522 {
523         if(!isdigit(resp[0]) || !isdigit(resp[1]) || !isdigit(resp[2])) {
524                 return 0;
525         }
526
527         if(isspace(resp[3])) {
528                 return atoi(resp);
529         }
530         if(resp[3] == '-') {
531                 return -atoi(resp);
532         }
533         return 0;
534 }
535
536 static void proc_control(struct ftp *ftp, const char *buf)
537 {
538         int code;
539         char *end;
540
541         while(*buf && isspace(*buf)) buf++;
542         if((end = strchr(buf, '\r'))) {
543                 *end = 0;
544         }
545
546         infomsg("recv: %s\n", buf);
547
548         if((code = respcode(buf)) == 0) {
549                 warnmsg("ignoring invalid response: %s\n", buf);
550                 return;
551         }
552         if(code < 0) {
553                 return; /* ignore continuations for now */
554         }
555
556         ftp->last_resp = code;
557
558         if(ftp->cproc) {
559                 if(ftp->cproc(ftp, code, buf, ftp->cproc_cls) <= 0) {
560                         ftp->cproc = 0;
561                         ftp->busy = 0;
562
563                         /* execute next operation if there's one queued */
564                         exec_queued(ftp);
565                 }
566                 return;
567         }
568         ftp->busy = 0;
569
570         switch(code) {
571         case 220:
572                 sendcmd(ftp, "user %s", ftp->user ? ftp->user : "anonymous");
573                 break;
574         case 331:
575                 sendcmd(ftp, "pass %s", ftp->pass ? ftp->pass : "foobar");
576                 break;
577         case 230:
578                 infomsg("login successful\n");
579                 if(newconn(ftp) == -1) {
580                         ftp_close(ftp);
581                 }
582                 break;
583         case 530:
584                 ftp->status = 0;
585                 errmsg("login failed\n");
586                 break;
587
588         default:
589                 ftp->busy = 0;
590
591                 /* execute next operation if there's one queued */
592                 exec_queued(ftp);
593         }
594 }
595
596 static int newconn(struct ftp *ftp)
597 {
598         ftp_queue(ftp, FTP_TYPE, "I");
599         ftp_queue(ftp, FTP_PWD, 0);
600         ftp_queue(ftp, FTP_LIST, 0);
601         return 0;
602 }
603
604 int ftp_update(struct ftp *ftp)
605 {
606         return -1;
607 }
608
609 int ftp_type(struct ftp *ftp, const char *type)
610 {
611         int tc = toupper(type[0]);
612         if(tc != 'I' && tc != 'A') {
613                 return -1;
614         }
615         sendcmd(ftp, "type %c", tc);
616         return 0;
617 }
618
619 int ftp_pwd(struct ftp *ftp)
620 {
621         sendcmd(ftp, "pwd");
622         ftp->cproc = cproc_pwd;
623         return 0;
624 }
625
626 int ftp_chdir(struct ftp *ftp, const char *dirname)
627 {
628         if(strcmp(dirname, "..") == 0) {
629                 sendcmd(ftp, "cdup");
630         } else {
631                 sendcmd(ftp, "cwd %s", dirname);
632         }
633         ftp->cproc = cproc_cwd;
634         return 0;
635 }
636
637 int ftp_mkdir(struct ftp *ftp, const char *dirname)
638 {
639         return -1;
640 }
641
642 int ftp_rmdir(struct ftp *ftp, const char *dirname)
643 {
644         return -1;
645 }
646
647 int ftp_delete(struct ftp *ftp, const char *fname)
648 {
649         return -1;
650 }
651
652 static int prepare_data(struct ftp *ftp)
653 {
654         if(ftp->data >= 0) {
655                 return 0;
656         }
657
658         if(ftp->passive) {
659                 ftp_passive(ftp);
660                 ftp_waitresp(ftp, TIMEOUT);
661         } else {
662                 if(ftp_active(ftp) == -1) {
663                         return -1;
664                 }
665         }
666         if(ftp->data == -1) {
667                 return -1;
668         }
669         return 0;
670 }
671
672 struct recvbuf {
673         char *buf;
674         long size, bufsz;
675 };
676
677 int ftp_list(struct ftp *ftp)
678 {
679         struct recvbuf *rbuf;
680
681         if(prepare_data(ftp) == -1) {
682                 return -1;
683         }
684
685         if(!(rbuf = malloc(sizeof *rbuf))) {
686                 errmsg("failed to allocate receive buffer\n");
687                 return -1;
688         }
689         rbuf->size = 0;
690         rbuf->bufsz = 1024;
691         if(!(rbuf->buf = malloc(rbuf->bufsz))) {
692                 free(rbuf);
693                 errmsg("failed to allocate receive buffer\n");
694                 return -1;
695         }
696
697         sendcmd(ftp, "list");
698         ftp->cproc = cproc_list;
699         ftp->dproc = dproc_list;
700         ftp->cproc_cls = ftp->dproc_cls = rbuf;
701         return 0;
702 }
703
704 int ftp_retrieve(struct ftp *ftp, const char *fname)
705 {
706         return -1;
707 }
708
709 int ftp_store(struct ftp *ftp, const char *fname)
710 {
711         return -1;
712 }
713
714 #define XFER_BUF_SIZE   1024
715
716 int ftp_transfer(struct ftp *ftp, struct ftp_transfer *xfer)
717 {
718         if(prepare_data(ftp) == -1) {
719                 return -1;
720         }
721
722         if(xfer->op == FTP_RETR) {
723                 sendcmd(ftp, "retr %s", xfer->rname);
724                 ftp->cproc = cproc_xfer;
725                 ftp->dproc = dproc_xfer;
726                 ftp->cproc_cls = ftp->dproc_cls = xfer;
727         } else {
728                 /* TODO */
729         }
730         return 0;
731 }
732
733 static int get_quoted_text(const char *str, char *buf)
734 {
735         int len;
736         const char *src, *end;
737
738         if(!(src = strchr(str, '"'))) {
739                 return -1;
740         }
741         src++;
742         end = src;
743         while(*end && *end != '"') end++;
744         if(!*end) return -1;
745
746         len = end - src;
747         memcpy(buf, src, len);
748         buf[len] = 0;
749         return 0;
750 }
751
752 static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls)
753 {
754         if(code != 200) {
755                 errmsg("ftp_active failed\n");
756                 ftp_close(ftp);
757         } else {
758                 ftp->status = FTP_CONN_ACT;
759         }
760         return 0;
761 }
762
763 static int cproc_pasv(struct ftp *ftp, int code, const char *buf, void *cls)
764 {
765         const char *str;
766         unsigned int addr[6];
767         struct sockaddr_in sa;
768         unsigned int ipaddr;
769         int port;
770
771         if(code != 227) {
772                 errmsg("ftp_passive failed\n");
773                 goto nopasv;
774         }
775
776         str = buf;
777         while(*str) {
778                 if(sscanf(str, "%u,%u,%u,%u,%u,%u", addr, addr + 1, addr + 2, addr + 3,
779                                         addr + 4, addr + 5) == 6) {
780                         break;
781                 }
782                 str++;
783         }
784         if(!*str || (addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]) >= 256) {
785                 errmsg("ftp_passive: failed to parse response: %s\n", buf);
786                 goto nopasv;
787         }
788         port = (addr[4] << 8) | addr[5];
789         ipaddr = ((unsigned int)addr[0] << 24) | ((unsigned int)addr[1] << 16) |
790                 ((unsigned int)addr[2] << 8) | addr[3];
791
792         if((ftp->data = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
793                 fprintf(stderr, "ftp_passive: failed to allocate socket\n");
794                 goto nopasv;
795         }
796
797         infomsg("passive mode: %d.%d.%d.%d port %d\n", addr[0], addr[1], addr[2], addr[3],
798                         port);
799
800         sa.sin_family = AF_INET;
801         sa.sin_addr.s_addr = htonl(ipaddr);
802         sa.sin_port = htons(port);
803
804         if(connect(ftp->data, (struct sockaddr*)&sa, sizeof sa) == -1) {
805                 errmsg("ftp_passive: failed to connect to %s:%p\n", inet_ntoa(sa.sin_addr), port);
806                 closesocket(ftp->data);
807                 ftp->data = -1;
808                 goto nopasv;
809         }
810
811         ftp->status = FTP_CONN_PASV;
812         return 0;
813
814 nopasv:
815         ftp->passive = 0;
816         ftp_active(ftp);
817         return 0;
818 }
819
820 static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
821 {
822         char *dirname;
823
824         if(code != 257) {
825                 warnmsg("pwd failed\n");
826                 return -1;
827         }
828
829         dirname = alloca(strlen(buf) + 1);
830         if(get_quoted_text(buf, dirname) == -1) {
831                 warnmsg("pwd: invalid response: %s\n", buf);
832                 return -1;
833         }
834
835         free(ftp->curdir);
836         ftp->curdir = strdup_nf(dirname);
837         ftp->modified = FTP_MOD_DIR;
838         return 0;
839 }
840
841 static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
842 {
843         if(code != 250) {
844                 warnmsg("cwd failed\n");
845                 return -1;
846         }
847
848         ftp_queue(ftp, FTP_PWD, 0);
849         ftp_queue(ftp, FTP_LIST, 0);
850         return 0;
851 }
852
853 static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls)
854 {
855         if(code < 200) {
856                 /* expect more */
857                 return 1;
858         }
859
860         if(code >= 400) {
861                 errmsg("failed to retrieve directory listing\n");
862         }
863         return 0;
864 }
865
866 #define SKIP_FIELD(p) \
867         do { \
868                 while(*(p) && *(p) != '\n' && !isspace(*(p))) (p)++; \
869                 while(*(p) && *(p) != '\n' && isspace(*(p))) (p)++; \
870         } while(0)
871
872 static int parse_dirent(struct ftp_dirent *ent, const char *line)
873 {
874         int len;
875         const char *ptr = line;
876         const char *end;
877
878         if(!(end = strchr(line, '\r')) && !(end = strchr(line, '\n'))) {
879                 return -1;
880         }
881
882         if(line[0] == 'd') {
883                 ent->type = FTP_DIR;
884         } else {
885                 ent->type = FTP_FILE;
886         }
887
888         SKIP_FIELD(ptr);                /* skip mode */
889         SKIP_FIELD(ptr);                /* skip links */
890         SKIP_FIELD(ptr);                /* skip owner */
891         SKIP_FIELD(ptr);                /* skip group */
892
893         if(ent->type == FTP_FILE) {
894                 ent->size = atoi(ptr);
895         }
896         SKIP_FIELD(ptr);                /* skip size */
897         SKIP_FIELD(ptr);                /* skip month */
898         SKIP_FIELD(ptr);                /* skip day */
899         SKIP_FIELD(ptr);                /* skip year */
900
901         if(ptr >= end) return -1;
902
903         len = end - ptr;
904         ent->name = malloc(len + 1);
905         memcpy(ent->name, ptr, len);
906         ent->name[len] = 0;
907
908         return 0;
909 }
910
911 int ftp_direntcmp(const void *a, const void *b)
912 {
913         const struct ftp_dirent *da = a, *db = b;
914
915         if(da->type == db->type) {
916                 return strcmp(da->name, db->name);
917         }
918         return da->type == FTP_DIR ? -1 : 1;
919 }
920
921 static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls)
922 {
923         int num;
924         struct recvbuf *rbuf = cls;
925
926         if(sz == 0) {
927                 /* EOF condition, we got the whole list, update directory entries */
928                 char *ptr = rbuf->buf;
929                 char *end = rbuf->buf + rbuf->size;
930                 struct ftp_dirent ent;
931
932                 darr_clear(ftp->dirent);
933
934                 while(ptr < end) {
935                         if(parse_dirent(&ent, ptr) != -1) {
936                                 darr_push(ftp->dirent, &ent);
937                         }
938                         while(ptr < end && *ptr != '\n' && *ptr != '\r') ptr++;
939                         while(ptr < end && (*ptr == '\r' || *ptr == '\n')) ptr++;
940                 }
941                 ftp->modified |= FTP_MOD_DIR;
942
943                 free(rbuf->buf);
944                 free(rbuf);
945                 ftp->dproc = 0;
946
947                 num = darr_size(ftp->dirent);
948                 qsort(ftp->dirent, num, sizeof *ftp->dirent, ftp_direntcmp);
949                 return;
950         }
951
952         if(rbuf->size + sz > rbuf->bufsz) {
953                 char *tmp;
954                 int newsz = rbuf->bufsz << 1;
955
956                 if(!(tmp = realloc(rbuf->buf, newsz))) {
957                         errmsg("failed to resize receive buffer\n");
958                         return;
959                 }
960                 rbuf->buf = tmp;
961                 rbuf->bufsz = newsz;
962         }
963
964         memcpy(rbuf->buf + rbuf->size, buf, sz);
965         rbuf->size += sz;
966 }
967
968 static int cproc_xfer(struct ftp *ftp, int code, const char *buf, void *cls)
969 {
970         char *ptr;
971         struct ftp_transfer *xfer = cls;
972
973         if(code < 200) {
974                 /* expect more */
975                 if(code == 150 && (ptr = strchr(buf, '('))) {
976                         /* update total size */
977                         sscanf(ptr, "(%ld bytes)", &xfer->total);
978                 }
979                 return 1;
980         }
981
982         if(code >= 400) {
983                 errmsg("failed to retrieve file\n");
984         }
985         return 0;
986 }
987
988 static void dproc_xfer(struct ftp *ftp, const char *buf, int sz, void *cls)
989 {
990         struct ftp_transfer *xfer = cls;
991
992         if(xfer->op == FTP_RETR) {
993                 if(sz == 0) {
994                         /* EOF condition, got the whole file */
995                         if(xfer->fp) {
996                                 fclose(xfer->fp);
997                                 xfer->fp = 0;
998                         }
999
1000                         if(xfer->done) {
1001                                 xfer->done(ftp, xfer);
1002                         }
1003                         return;
1004                 }
1005
1006                 if(xfer->fp) {
1007                         ((char*)buf)[sz] = 0;
1008                         fwrite(buf, 1, sz, xfer->fp);
1009
1010                 } else if(xfer->mem) {
1011                         int prevsz = darr_size(xfer->mem);
1012                         darr_resize(xfer->mem, prevsz + sz);
1013                         memcpy(xfer->mem + prevsz, buf, sz);
1014                 }
1015
1016                 xfer->count += sz;
1017                 if(xfer->count > xfer->total) {
1018                         xfer->count = xfer->total;
1019                 }
1020
1021         } else { /* FTP_STOR */
1022                 /* TODO */
1023         }
1024 }
1025
1026 const char *ftp_curdir(struct ftp *ftp)
1027 {
1028         return ftp->curdir;
1029 }
1030
1031 int ftp_num_dirent(struct ftp *ftp)
1032 {
1033         return darr_size(ftp->dirent);
1034 }
1035
1036 struct ftp_dirent *ftp_dirent(struct ftp *ftp, int idx)
1037 {
1038         return ftp->dirent + idx;
1039 }