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