07cec4513d365e0ace40d896693d6cd387704b70
[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 = darr_alloc(0, sizeof *ftp->dirent);
53         return ftp;
54 }
55
56 void ftp_free(struct ftp *ftp)
57 {
58         if(!ftp) return;
59
60         if(ftp->ctl >= 0) {
61                 ftp_close(ftp);
62         }
63
64         free_dir(ftp->dirent);
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 static int ftp_passive(struct ftp *ftp)
334 {
335         if(ftp->lis >= 0) {
336                 closesocket(ftp->lis);
337                 ftp->lis = -1;
338         }
339         if(ftp->data >= 0) {
340                 closesocket(ftp->data);
341                 ftp->data = -1;
342         }
343
344         sendcmd(ftp, "PASV");
345         ftp->cproc = cproc_pasv;
346         return 0;
347 }
348
349 int ftp_handle(struct ftp *ftp, int s)
350 {
351         if(s == ftp->ctl) {
352                 return handle_control(ftp);
353         }
354         if(s == ftp->data) {
355                 return handle_data(ftp, s);
356         }
357         if(s == ftp->lis) {
358                 int ns = accept(s, 0, 0);
359                 if(ftp->data >= 0) {
360                         closesocket(ns);
361                 } else {
362                         ftp->data = ns;
363                 }
364                 return 0;
365         }
366         return -1;
367 }
368
369 static int sendcmd(struct ftp *ftp, const char *fmt, ...)
370 {
371         char buf[256];
372         va_list ap;
373
374         if(ftp->ctl < 0) {
375                 return -1;
376         }
377
378         ftp->busy = 1;
379
380         va_start(ap, fmt);
381         vsprintf(buf, fmt, ap);
382         va_end(ap);
383         infomsg("send: %s\n", buf);
384         strcat(buf, "\r\n");
385         return send(ftp->ctl, buf, strlen(buf), 0);
386 }
387
388 static int handle_control(struct ftp *ftp)
389 {
390         int i, sz, rd;
391         char *buf, *start, *end;
392
393         for(;;) {
394                 if((sz = sizeof ftp->crecv - ftp->num_crecv) <= 0) {
395                         /* discard buffer */
396                         warnmsg("discard buffer\n");
397                         sz = sizeof ftp->crecv;
398                         ftp->num_crecv = 0;
399                 }
400                 start = ftp->crecv + ftp->num_crecv;
401                 if((rd = recv(ftp->ctl, start, sz, 0)) == -1) {
402                         if(errno == EINTR) continue;
403                         /* assume EWOULDBLOCK, try again next time */
404                         return 0;
405                 }
406                 if(rd == 0) {
407                         ftp_close(ftp);
408                         return -1;
409                 }
410
411                 end = start + rd;
412                 buf = ftp->crecv;
413                 for(i=0; i<rd; i++) {
414                         if(start[i] == '\r') {
415                                 start[i] = 0;
416                         } else if(start[i] == '\n') {
417                                 start[i] = 0;
418                                 proc_control(ftp, buf);
419                                 buf = start + i + 1;
420                         }
421                 }
422                 if(buf != ftp->crecv && buf < end) {
423                         ftp->num_crecv = end - buf;
424                         memmove(ftp->crecv, buf, ftp->num_crecv);
425                 } else {
426                         ftp->num_crecv = 0;
427                 }
428         }
429         return 0;
430 }
431
432 static int handle_data(struct ftp *ftp, int s)
433 {
434         int rd;
435
436         if(ftp->data == -1) {
437                 return -1;
438         }
439
440         for(;;) {
441                 if((rd = recv(ftp->data, ftp->drecv, sizeof ftp->drecv, 0)) == -1) {
442                         if(errno == EINTR) continue;
443                         /* assume EWOULDBLOCK, try again next time */
444                         break;
445                 }
446
447                 /* call the callback first, so that we'll get a 0-count call to indicate
448                  * EOF when the server closes the data connection
449                  */
450                 if(ftp->dproc) {
451                         ftp->dproc(ftp, ftp->drecv, rd, ftp->dproc_cls);
452                 }
453
454                 if(rd == 0) {
455                         closesocket(ftp->data);
456                         ftp->data = -1;
457                         return -1;
458                 }
459         }
460         return 0;
461 }
462
463 static int respcode(const char *resp)
464 {
465         if(!isdigit(resp[0]) || !isdigit(resp[1]) || !isdigit(resp[2])) {
466                 return 0;
467         }
468
469         if(isspace(resp[3])) {
470                 return atoi(resp);
471         }
472         if(resp[3] == '-') {
473                 return -atoi(resp);
474         }
475         return 0;
476 }
477
478 static void proc_control(struct ftp *ftp, const char *buf)
479 {
480         int code;
481         char *end;
482
483         while(*buf && isspace(*buf)) buf++;
484         if((end = strchr(buf, '\r'))) {
485                 *end = 0;
486         }
487
488         infomsg("recv: %s\n", buf);
489
490         if((code = respcode(buf)) == 0) {
491                 warnmsg("ignoring invalid response: %s\n", buf);
492                 return;
493         }
494         if(code < 0) {
495                 return; /* ignore continuations for now */
496         }
497
498         ftp->last_resp = code;
499
500         if(ftp->cproc) {
501                 if(ftp->cproc(ftp, code, buf, ftp->cproc_cls) <= 0) {
502                         ftp->cproc = 0;
503                         ftp->busy = 0;
504
505                         /* execute next operation if there's one queued */
506                         exec_queued(ftp);
507                 }
508                 return;
509         }
510         ftp->busy = 0;
511
512         switch(code) {
513         case 220:
514                 sendcmd(ftp, "user %s", ftp->user ? ftp->user : "anonymous");
515                 break;
516         case 331:
517                 sendcmd(ftp, "pass %s", ftp->pass ? ftp->pass : "foobar");
518                 break;
519         case 230:
520                 infomsg("login successful\n");
521                 if(newconn(ftp) == -1) {
522                         ftp_close(ftp);
523                 }
524                 break;
525         case 530:
526                 ftp->status = 0;
527                 errmsg("login failed\n");
528                 break;
529         }
530 }
531
532 static int newconn(struct ftp *ftp)
533 {
534         ftp_queue(ftp, FTP_PWD, 0);
535         ftp_queue(ftp, FTP_LIST, 0);
536         return 0;
537 }
538
539 int ftp_update(struct ftp *ftp)
540 {
541         return -1;
542 }
543
544 int ftp_pwd(struct ftp *ftp)
545 {
546         sendcmd(ftp, "pwd");
547         ftp->cproc = cproc_pwd;
548         return 0;
549 }
550
551 int ftp_chdir(struct ftp *ftp, const char *dirname)
552 {
553         if(strcmp(dirname, "..") == 0) {
554                 sendcmd(ftp, "cdup");
555         } else {
556                 sendcmd(ftp, "cwd %s", dirname);
557         }
558         ftp->cproc = cproc_cwd;
559         return 0;
560 }
561
562 int ftp_mkdir(struct ftp *ftp, const char *dirname)
563 {
564         return -1;
565 }
566
567 int ftp_rmdir(struct ftp *ftp, const char *dirname)
568 {
569         return -1;
570 }
571
572 int ftp_delete(struct ftp *ftp, const char *fname)
573 {
574         return -1;
575 }
576
577 static int prepare_data(struct ftp *ftp)
578 {
579         if(ftp->data >= 0) {
580                 return 0;
581         }
582
583         if(ftp->passive) {
584                 ftp_passive(ftp);
585                 ftp_waitresp(ftp, TIMEOUT);
586         } else {
587                 if(ftp_active(ftp) == -1) {
588                         return -1;
589                 }
590         }
591         if(ftp->data == -1) {
592                 return -1;
593         }
594         return 0;
595 }
596
597 struct recvbuf {
598         char *buf;
599         long size, bufsz;
600 };
601
602 int ftp_list(struct ftp *ftp)
603 {
604         struct recvbuf *rbuf;
605
606         if(prepare_data(ftp)) {
607                 return -1;
608         }
609
610         if(!(rbuf = malloc(sizeof *rbuf))) {
611                 errmsg("failed to allocate receive buffer\n");
612                 return -1;
613         }
614         rbuf->size = 0;
615         rbuf->bufsz = 1024;
616         if(!(rbuf->buf = malloc(rbuf->bufsz))) {
617                 free(rbuf);
618                 errmsg("failed to allocate receive buffer\n");
619                 return -1;
620         }
621
622         sendcmd(ftp, "list");
623         ftp->cproc = cproc_list;
624         ftp->dproc = dproc_list;
625         ftp->cproc_cls = ftp->dproc_cls = rbuf;
626         return 0;
627 }
628
629 int ftp_retrieve(struct ftp *ftp, const char *fname)
630 {
631         return -1;
632 }
633
634 int ftp_store(struct ftp *ftp, const char *fname)
635 {
636         return -1;
637 }
638
639 static int get_quoted_text(const char *str, char *buf)
640 {
641         int len;
642         const char *src, *end;
643
644         if(!(src = strchr(str, '"'))) {
645                 return -1;
646         }
647         src++;
648         end = src;
649         while(*end && *end != '"') end++;
650         if(!*end) return -1;
651
652         len = end - src;
653         memcpy(buf, src, len);
654         buf[len] = 0;
655         return 0;
656 }
657
658 static int cproc_active(struct ftp *ftp, int code, const char *buf, void *cls)
659 {
660         if(code != 200) {
661                 errmsg("ftp_active failed\n");
662                 ftp_close(ftp);
663         } else {
664                 ftp->status = FTP_CONN_ACT;
665         }
666         return 0;
667 }
668
669 static int cproc_pasv(struct ftp *ftp, int code, const char *buf, void *cls)
670 {
671         const char *str;
672         unsigned int addr[6];
673         struct sockaddr_in sa;
674         unsigned int ipaddr;
675         int port;
676
677         if(code != 227) {
678                 errmsg("ftp_passive failed\n");
679                 goto nopasv;
680         }
681
682         str = buf;
683         while(*str) {
684                 if(sscanf(str, "%u,%u,%u,%u,%u,%u", addr, addr + 1, addr + 2, addr + 3,
685                                         addr + 4, addr + 5) == 6) {
686                         break;
687                 }
688                 str++;
689         }
690         if(!*str || (addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]) >= 256) {
691                 errmsg("ftp_passive: failed to parse response: %s\n", buf);
692                 goto nopasv;
693         }
694         port = (addr[4] << 8) | addr[5];
695         ipaddr = ((uint32_t)addr[0] << 24) | ((uint32_t)addr[1] << 16) |
696                 ((uint32_t)addr[2] << 8) | addr[3];
697
698         if((ftp->data = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
699                 fprintf(stderr, "ftp_passive: failed to allocate socket\n");
700                 goto nopasv;
701         }
702
703         infomsg("passive mode: %d.%d.%d.%d port %d\n", addr[0], addr[1], addr[2], addr[3],
704                         port);
705
706         sa.sin_family = AF_INET;
707         sa.sin_addr.s_addr = htonl(ipaddr);
708         sa.sin_port = htons(port);
709
710         if(connect(ftp->data, (struct sockaddr*)&sa, sizeof sa) == -1) {
711                 errmsg("ftp_passive: failed to connect to %s:%p\n", inet_ntoa(sa.sin_addr), port);
712                 close(ftp->data);
713                 ftp->data = -1;
714                 goto nopasv;
715         }
716
717         ftp->status = FTP_CONN_PASV;
718         return 0;
719
720 nopasv:
721         ftp->passive = 0;
722         ftp_active(ftp);
723         return 0;
724 }
725
726 static int cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
727 {
728         char *dirname;
729
730         if(code != 257) {
731                 warnmsg("pwd failed\n");
732                 return -1;
733         }
734
735         dirname = alloca(strlen(buf) + 1);
736         if(get_quoted_text(buf, dirname) == -1) {
737                 warnmsg("pwd: invalid response: %s\n", buf);
738                 return -1;
739         }
740
741         free(ftp->curdir);
742         ftp->curdir = strdup_nf(dirname);
743         ftp->modified = FTP_MOD_DIR;
744         return 0;
745 }
746
747 static int cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
748 {
749         if(code != 250) {
750                 warnmsg("cwd failed\n");
751                 return -1;
752         }
753
754         ftp_queue(ftp, FTP_PWD, 0);
755         ftp_queue(ftp, FTP_LIST, 0);
756         return 0;
757 }
758
759 static int cproc_list(struct ftp *ftp, int code, const char *buf, void *cls)
760 {
761         if(code < 200) {
762                 /* expect more */
763                 return 1;
764         }
765
766         if(code >= 400) {
767                 errmsg("failed to retrieve directory listing\n");
768         }
769         return 0;
770 }
771
772 #define SKIP_FIELD(p) \
773         do { \
774                 while(*(p) && *(p) != '\n' && !isspace(*(p))) (p)++; \
775                 while(*(p) && *(p) != '\n' && isspace(*(p))) (p)++; \
776         } while(0)
777
778 static int parse_dirent(struct ftp_dirent *ent, const char *line)
779 {
780         int len;
781         const char *ptr = line;
782         const char *end;
783
784         if(!(end = strchr(line, '\r')) && !(end = strchr(line, '\n'))) {
785                 return -1;
786         }
787
788         if(line[0] == 'd') {
789                 ent->type = FTP_DIR;
790         } else {
791                 ent->type = FTP_FILE;
792         }
793
794         SKIP_FIELD(ptr);                /* skip mode */
795         SKIP_FIELD(ptr);                /* skip links */
796         SKIP_FIELD(ptr);                /* skip owner */
797         SKIP_FIELD(ptr);                /* skip group */
798
799         if(ent->type == FTP_FILE) {
800                 ent->size = atoi(ptr);
801         }
802         SKIP_FIELD(ptr);                /* skip size */
803         SKIP_FIELD(ptr);                /* skip month */
804         SKIP_FIELD(ptr);                /* skip day */
805         SKIP_FIELD(ptr);                /* skip year */
806
807         if(ptr >= end) return -1;
808
809         len = end - ptr;
810         ent->name = malloc(len + 1);
811         memcpy(ent->name, ptr, len);
812         ent->name[len] = 0;
813
814         return 0;
815 }
816
817 int ftp_direntcmp(const void *a, const void *b)
818 {
819         const struct ftp_dirent *da = a, *db = b;
820
821         if(da->type == db->type) {
822                 return strcmp(da->name, db->name);
823         }
824         return da->type == FTP_DIR ? -1 : 1;
825 }
826
827 static void dproc_list(struct ftp *ftp, const char *buf, int sz, void *cls)
828 {
829         int num;
830         struct recvbuf *rbuf = cls;
831
832         if(sz == 0) {
833                 /* EOF condition, we got the whole list, update directory entries */
834                 char *ptr = rbuf->buf;
835                 char *end = rbuf->buf + rbuf->size;
836                 struct ftp_dirent ent;
837
838                 darr_clear(ftp->dirent);
839
840                 while(ptr < end) {
841                         if(parse_dirent(&ent, ptr) != -1) {
842                                 darr_push(ftp->dirent, &ent);
843                         }
844                         while(ptr < end && *ptr != '\n' && *ptr != '\r') ptr++;
845                         while(ptr < end && (*ptr == '\r' || *ptr == '\n')) ptr++;
846                 }
847                 ftp->modified |= FTP_MOD_DIR;
848
849                 free(rbuf->buf);
850                 free(rbuf);
851                 ftp->dproc = 0;
852
853                 num = darr_size(ftp->dirent);
854                 qsort(ftp->dirent, num, sizeof *ftp->dirent, ftp_direntcmp);
855                 return;
856         }
857
858         if(rbuf->size + sz > rbuf->bufsz) {
859                 char *tmp;
860                 int newsz = rbuf->bufsz << 1;
861
862                 if(!(tmp = realloc(rbuf->buf, newsz))) {
863                         errmsg("failed to resize receive buffer\n");
864                         return;
865                 }
866                 rbuf->buf = tmp;
867                 rbuf->bufsz = newsz;
868         }
869
870         memcpy(rbuf->buf + rbuf->size, buf, sz);
871         rbuf->size += sz;
872 }
873
874 const char *ftp_curdir(struct ftp *ftp)
875 {
876         return ftp->curdir;
877 }
878
879 int ftp_num_dirent(struct ftp *ftp)
880 {
881         return darr_size(ftp->dirent);
882 }
883
884 struct ftp_dirent *ftp_dirent(struct ftp *ftp, int idx)
885 {
886         return ftp->dirent + idx;
887 }