2007-02-08 Marcus Brinkmann <marcus@g10code.de>
[oftpd.git] / src / ftp_session.c
1 /* 
2  * $Id$
3  */
4
5 #include <config.h>
6 #include <string.h>
7 #include <stdio.h>
8 #include <stdarg.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11 #include <sys/types.h>
12 #include <fcntl.h>
13 #include <sys/socket.h>
14 #include <errno.h>
15 #include <pthread.h>
16 #include <sys/utsname.h>
17 #include <netdb.h>
18 #include <syslog.h>
19 #include <stdlib.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
22
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
25 # include <time.h>
26 #else
27 # if HAVE_SYS_TIME_H
28 #  include <sys/time.h>
29 # else
30 #  include <time.h>
31 # endif
32 #endif
33
34 #if HAVE_SYS_SENDFILE_H
35 #include <sys/sendfile.h>
36 #endif
37
38 #include "daemon_assert.h"
39 #include "telnet_session.h"
40 #include "ftp_command.h"
41 #include "file_list.h"
42 #include "ftp_session.h"
43 #include "watchdog.h"
44 #include "oftpd.h"
45 #include "af_portability.h"
46
47 /* space requirements */
48 #define ADDRPORT_STRLEN 58
49
50 /* prototypes */
51 static int invariant(const ftp_session_t *f);
52 static void reply(ftp_session_t *f, int code, const char *fmt, ...);
53 static void change_dir(ftp_session_t *f, const char *new_dir);
54 static int open_connection(ftp_session_t *f);
55 static int write_fully(int fd, const char *buf, int buflen);
56 static void init_passive_port();
57 static int get_passive_port();
58 static int convert_newlines(char *dst, const char *src, int srclen);
59 static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz);
60 static void send_readme(const ftp_session_t *f, int code);
61 static void netscape_hack(int fd);
62 static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port);
63 static int set_pasv(ftp_session_t *f, sockaddr_storage_t *host_port);
64 static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b);
65 static void get_absolute_fname(char *fname, 
66                                int fname_len,
67                                const char *dir,
68                                const char *file);
69
70 /* Common cleanup handler.  */
71 static void fd_cleanup (int *fd)
72 {
73   if (*fd != -1)
74     close (*fd);
75 }
76
77 /* command handlers */
78 static void do_user(ftp_session_t *f, const ftp_command_t *cmd);
79 static void do_pass(ftp_session_t *f, const ftp_command_t *cmd);
80 static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd);
81 static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd);
82 static void do_quit(ftp_session_t *f, const ftp_command_t *cmd);
83 static void do_port(ftp_session_t *f, const ftp_command_t *cmd);
84 static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd);
85 static void do_type(ftp_session_t *f, const ftp_command_t *cmd);
86 static void do_stru(ftp_session_t *f, const ftp_command_t *cmd);
87 static void do_mode(ftp_session_t *f, const ftp_command_t *cmd);
88 static void do_retr(ftp_session_t *f, const ftp_command_t *cmd);
89 static void do_stor(ftp_session_t *f, const ftp_command_t *cmd);
90 static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd);
91 static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd);
92 static void do_list(ftp_session_t *f, const ftp_command_t *cmd);
93 static void do_syst(ftp_session_t *f, const ftp_command_t *cmd);
94 static void do_noop(ftp_session_t *f, const ftp_command_t *cmd);
95 static void do_rest(ftp_session_t *f, const ftp_command_t *cmd);
96 static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd);
97 static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd);
98 static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd);
99 static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd);
100 static void do_size(ftp_session_t *f, const ftp_command_t *cmd);
101 static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd);
102
103 static struct {
104     char *name;
105     void (*func)(ftp_session_t *f, const ftp_command_t *cmd);
106 } command_func[] = {
107     { "USER", do_user },
108     { "PASS", do_pass },
109     { "CWD",  do_cwd },
110     { "CDUP", do_cdup },
111     { "QUIT", do_quit },
112     { "PORT", do_port },
113     { "PASV", do_pasv },
114     { "LPRT", do_lprt },
115     { "LPSV", do_lpsv },
116     { "EPRT", do_eprt },
117     { "EPSV", do_epsv },
118     { "TYPE", do_type },
119     { "STRU", do_stru },
120     { "MODE", do_mode },
121     { "RETR", do_retr },
122     { "STOR", do_stor },
123     { "PWD",  do_pwd },
124     { "NLST", do_nlst },
125     { "LIST", do_list },
126     { "SYST", do_syst },
127     { "NOOP", do_noop },
128     { "REST", do_rest },
129     { "SIZE", do_size },
130     { "MDTM", do_mdtm }
131 };
132
133 #define NUM_COMMAND_FUNC (sizeof(command_func) / sizeof(command_func[0]))
134
135
136 int ftp_session_init(ftp_session_t *f, 
137                      const sockaddr_storage_t *client_addr, 
138                      const sockaddr_storage_t *server_addr, 
139                      telnet_session_t *t, 
140                      const char *dir,
141                      error_t *err)
142 {
143     daemon_assert(f != NULL);
144     daemon_assert(client_addr != NULL);
145     daemon_assert(server_addr != NULL);
146     daemon_assert(t != NULL);
147     daemon_assert(dir != NULL);
148     daemon_assert(strlen(dir) <= PATH_MAX);
149     daemon_assert(err != NULL);
150
151 #ifdef INET6
152     /* if the control connection is on IPv6, we need to get an IPv4 address */
153     /* to bind the socket to */
154     if (SSFAM(server_addr) == AF_INET6) {
155         struct addrinfo hints;
156         struct addrinfo *res;
157         int errcode;
158
159         /* getaddrinfo() does the job nicely */
160         memset(&hints, 0, sizeof(struct addrinfo));
161         hints.ai_family = AF_INET;
162         hints.ai_flags = AI_PASSIVE;
163         if (getaddrinfo(NULL, "ftp", &hints, &res) != 0) {
164             char errbuf[ERRBUF_SIZE];
165             error_init(err, 0, "unable to determing IPv4 address; %s",
166                 gai_strerror_r(errcode, errbuf, ERRBUF_SIZE));
167             return 0;
168         }
169
170         /* let's sanity check */
171         daemon_assert(res != NULL);
172         daemon_assert(sizeof(f->server_ipv4_addr) >= res->ai_addrlen);
173         daemon_assert(SSFAM(host_port) == AF_INET);
174
175         /* copy the result and free memory as necessary */
176         memcpy(&f->server_ipv4_addr, res->ai_addr, res->ai_addrlen);
177         freeaddrinfo(res);
178     } else {
179         daemon_assert(SSFAM(host_port) == AF_INET);
180         f->server_ipv4_addr = *server_addr;
181     }
182 #else
183     f->server_ipv4_addr = *server_addr;
184 #endif
185
186     f->session_active = 1;
187     f->command_number = 0;
188
189     f->data_type = TYPE_ASCII;
190     f->file_structure = STRU_FILE;
191
192     f->file_offset = 0;
193     f->file_offset_command_number = ULONG_MAX;
194
195     f->epsv_all_set = 0;
196
197     f->client_addr = *client_addr;
198     get_addr_str(client_addr, f->client_addr_str, sizeof(f->client_addr_str));
199
200     f->server_addr = *server_addr;
201
202     f->telnet_session = t;
203     daemon_assert(strlen(dir) < sizeof(f->dir));
204     strcpy(f->dir, dir);
205
206     f->data_channel = DATA_PORT;
207     f->data_port = *client_addr;
208     f->server_fd = -1;
209
210     daemon_assert(invariant(f));
211
212     return 1;
213 }
214
215 void ftp_session_drop(ftp_session_t *f, const char *reason)
216 {
217     daemon_assert(invariant(f));
218     daemon_assert(reason != NULL);
219
220     /* say goodbye */
221     reply(f, 421, "%s.", reason);
222
223     daemon_assert(invariant(f));
224 }
225
226 void ftp_session_run(ftp_session_t *f, watched_t *watched)
227 {
228     char buf[2048];
229     int len;
230     ftp_command_t cmd;
231     int i;
232
233     daemon_assert(invariant(f));
234     daemon_assert(watched != NULL);
235
236     /* record our watchdog */
237     f->watched = watched;
238
239     /* say hello */
240     send_readme(f, 220);
241     reply(f, 220, "Service ready for new user.");
242
243     /* process commands */
244     while (f->session_active && 
245         telnet_session_readln(f->telnet_session, buf, sizeof(buf))) 
246     {
247      
248         /* delay our timeout based on this input */
249         watchdog_defer_watched(f->watched);
250
251         /* increase our command count */
252         if (f->command_number == ULONG_MAX) {
253             f->command_number = 0;
254         } else {
255             f->command_number++;
256         }
257     
258         /* make sure we read a whole line */
259         len = strlen(buf);
260         if (buf[len-1] != '\n') {
261             reply(f, 500, "Command line too long.");
262             while (telnet_session_readln(f->telnet_session, buf, sizeof(buf))) {
263                 len = strlen(buf);
264                 if (buf[len-1] == '\n') {
265                     break;
266                 }
267             }
268             goto next_command;
269         }
270
271         syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
272
273         /* parse the line */
274         if (!ftp_command_parse(buf, &cmd)) {
275             reply(f, 500, "Syntax error, command unrecognized.");
276             goto next_command;
277         }
278
279         /* dispatch the command */
280         for (i=0; i<NUM_COMMAND_FUNC; i++) {
281             if (strcmp(cmd.command, command_func[i].name) == 0) {
282                 (command_func[i].func)(f, &cmd);
283                 goto next_command;
284             }
285         }
286
287         /* oops, we don't have this command (shouldn't happen - shrug) */
288         reply(f, 502, "Command not implemented.");
289
290 next_command: {}
291     }
292
293     daemon_assert(invariant(f));
294 }
295
296 void ftp_session_destroy(ftp_session_t *f) 
297 {
298     daemon_assert(invariant(f));
299
300     if (f->server_fd != -1) {
301         close(f->server_fd);
302         f->server_fd = -1;
303     }
304 }
305
306 #ifndef NDEBUG
307 static int invariant(const ftp_session_t *f)
308 {
309     int len;
310
311     if (f == NULL) {
312         return 0;
313     }
314     if ((f->session_active != 0) && (f->session_active != 1)) {
315         return 0;
316     }
317     if ((f->data_type != TYPE_ASCII) && (f->data_type != TYPE_IMAGE)) {
318         return 0;
319     }
320     if ((f->file_structure != STRU_FILE) && (f->file_structure != STRU_RECORD)){
321         return 0;
322     }
323     if (f->file_offset < 0) {
324         return 0;
325     }
326     if ((f->epsv_all_set != 0) && (f->epsv_all_set != 1)) {
327         return 0;
328     }
329     len = strlen(f->client_addr_str);
330     if ((len <= 0) || (len >= ADDRPORT_STRLEN)) {
331         return 0;
332     }
333     if (f->telnet_session == NULL) {
334         return 0;
335     }
336     switch (f->data_channel) {
337         case DATA_PORT:
338             /* If the client specifies a port, verify that it is from the   */
339             /* host the client connected from.  This prevents a client from */
340             /* using the server to open connections to arbritrary hosts.    */
341             if (!ip_equal(&f->client_addr, &f->data_port)) {
342                 return 0;
343             }
344             if (f->server_fd != -1) {
345                 return 0;
346             }
347             break;
348         case DATA_PASSIVE:
349             if (f->server_fd < 0) {
350                 return 0;
351             }
352             break;
353         default:
354             return 0;
355     }
356     return 1;
357 }
358 #endif /* NDEBUG */
359
360 static void reply(ftp_session_t *f, int code, const char *fmt, ...)
361 {
362     char buf[256];
363     va_list ap;
364
365     daemon_assert(invariant(f));
366     daemon_assert(code >= 100);
367     daemon_assert(code <= 559);
368     daemon_assert(fmt != NULL);
369
370     /* prepend our code to the buffer */
371     sprintf(buf, "%d", code);
372     buf[3] = ' ';
373
374     /* add the formatted output of the caller to the buffer */
375     va_start(ap, fmt);
376     vsnprintf(buf+4, sizeof(buf)-4, fmt, ap);
377     va_end(ap);
378
379     /* log our reply */
380     syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
381
382     /* send the output to the other side */
383     telnet_session_println(f->telnet_session, buf);
384
385     daemon_assert(invariant(f));
386 }
387
388 static void do_user(ftp_session_t *f, const ftp_command_t *cmd) 
389 {
390     const char *user;
391
392     daemon_assert(invariant(f));
393     daemon_assert(cmd != NULL);
394     daemon_assert(cmd->num_arg == 1);
395
396     user = cmd->arg[0].string;
397     if (strcasecmp(user, "ftp") && strcasecmp(user, "anonymous")) {
398         syslog(LOG_WARNING, "%s attempted to log in as \"%s\"", 
399             f->client_addr_str, user);
400         reply(f, 530, "Only anonymous FTP supported.");
401     } else {
402         reply(f, 331, "Send e-mail address as password.");
403     }
404     daemon_assert(invariant(f));
405 }
406
407
408 static void do_pass(ftp_session_t *f, const ftp_command_t *cmd) 
409 {
410     const char *password;
411
412     daemon_assert(invariant(f));
413     daemon_assert(cmd != NULL);
414     daemon_assert(cmd->num_arg == 1);
415
416     password = cmd->arg[0].string;
417     syslog(LOG_INFO, "%s reports e-mail address \"%s\"", 
418         f->client_addr_str, password);
419     reply(f, 230, "User logged in, proceed.");
420
421     daemon_assert(invariant(f));
422 }
423
424 #ifdef INET6
425 static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
426 {
427     int port;
428     int error;
429     int len;
430
431     daemon_assert(s != NULL);
432     daemon_assert(buf != NULL);
433
434     /* buf must be able to contain (at least) a string representing an
435      * ipv4 addr, followed by the string " port " (6 chars) and the port
436      * number (which is 5 chars max), plus the '\0' character. */ 
437     daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
438
439     error = getnameinfo(client_addr, sizeof(sockaddr_storage_t), buf, 
440                 bufsiz, NULL, 0, NI_NUMERICHOST);
441     /* getnameinfo() should never fail when called with NI_NUMERICHOST */
442     daemon_assert(error == 0);
443
444     len = strlen(buf);
445     daemon_assert(bufsiz >= len+12);
446     snprintf(buf+len, bufsiz-len, " port %d", ntohs(SINPORT(&f->client_addr)));
447 }
448 #else
449 static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
450 {
451     unsigned int addr;
452     int port;
453
454     daemon_assert(s != NULL);
455     daemon_assert(buf != NULL);
456
457     /* buf must be able to contain (at least) a string representing an
458      * ipv4 addr, followed by the string " port " (6 chars) and the port
459      * number (which is 5 chars max), plus the '\0' character. */ 
460     daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
461
462     addr = ntohl(s->sin_addr.s_addr);
463     port = ntohs(s->sin_port);
464     snprintf(buf, bufsiz, "%d.%d.%d.%d port %d", 
465         (addr >> 24) & 0xff, 
466         (addr >> 16) & 0xff,
467         (addr >> 8)  & 0xff,
468         addr & 0xff,
469         port);
470 }
471 #endif
472
473 static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd) 
474 {
475     const char *new_dir;
476
477     daemon_assert(invariant(f));
478     daemon_assert(cmd != NULL);
479     daemon_assert(cmd->num_arg == 1);
480
481     new_dir = cmd->arg[0].string;
482     change_dir(f, new_dir);
483
484     daemon_assert(invariant(f));
485 }
486
487 static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd) 
488 {
489     daemon_assert(invariant(f));
490     daemon_assert(cmd != NULL);
491     daemon_assert(cmd->num_arg == 0);
492
493     change_dir(f, "..");
494
495     daemon_assert(invariant(f));
496 }
497
498 static void change_dir(ftp_session_t *f, const char *new_dir)
499 {
500     char target[PATH_MAX+1];
501     const char *p, *n;
502     int len;
503     char *prev_dir;
504     char *target_end;
505
506     struct stat stat_buf;
507     int dir_okay;
508
509     daemon_assert(invariant(f));
510     daemon_assert(new_dir != NULL);
511     daemon_assert(strlen(new_dir) <= PATH_MAX);
512
513     /* set up our "base" directory that we build from */
514     p = new_dir;
515     if (*p == '/') {
516         /* if this starts with a '/' it is an absolute path */
517         strcpy(target, "/");
518         do {
519             p++;
520         } while (*p == '/');
521     } else {
522         /* otherwise it's a relative path */
523         daemon_assert(strlen(f->dir) < sizeof(target));
524         strcpy(target, f->dir);
525     }
526
527     /* add on each directory, handling "." and ".." */
528     while (*p != '\0') {
529
530         /* find the end of the next directory (either at '/' or '\0') */
531         n = strchr(p, '/');
532         if (n == NULL) {
533             n = strchr(p, '\0');
534         }
535         len = n - p;
536
537         if ((len == 1) && (p[0] == '.')) {
538
539             /* do nothing with "." */
540
541         } else if ((len == 2) && (p[0] == '.') && (p[1] == '.')) {
542
543             /* change to previous directory with ".." */
544             prev_dir = strrchr(target, '/');
545             daemon_assert(prev_dir != NULL);
546             *prev_dir = '\0';
547             if (prev_dir == target) {
548                 strcpy(target, "/");
549             }
550
551         } else {
552
553             /* otherwise add to current directory */
554             if ((strlen(target) + 1 + len) > PATH_MAX) {
555                 reply(f, 550, "Error changing directory; path is too long.");
556                 return;
557             }
558
559             /* append a '/' unless we were at the root directory */
560             target_end = strchr(target, '\0');
561             if (target_end != target+1) {
562                 *target_end++ = '/';
563             }
564
565             /* add the directory itself */
566             while (p != n) {
567                 *target_end++ = *p++;
568             }
569             *target_end = '\0';
570
571         }
572
573         /* advance to next directory to check */
574         p = n;
575
576         /* skip '/' characters */
577         while (*p == '/') {
578             p++;
579         }
580     }
581
582     /* see if this is a directory we can change into.  We don't allow
583        /dev as usual. */
584     dir_okay = 0;
585     if (!(!strncmp (target, "/dev", 4) && (!target[4] || target[4] == '/'))
586         && stat(target, &stat_buf) == 0) {
587 #ifndef STAT_MACROS_BROKEN
588         if (!S_ISDIR(stat_buf.st_mode)) {
589 #else
590         if (S_ISDIR(stat_buf.st_mode)) {
591 #endif
592             reply(f, 550,"Directory change failed; target is not a directory.");
593         } else { 
594             if (S_IXOTH & stat_buf.st_mode) {
595                 dir_okay = 1;
596             } else if ((stat_buf.st_gid == getegid()) && 
597                 (S_IXGRP & stat_buf.st_mode)) 
598             {
599                 dir_okay = 1;
600             } else if ((stat_buf.st_uid == geteuid()) && 
601                 (S_IXUSR & stat_buf.st_mode)) 
602             {
603                 dir_okay = 1;
604             } else {
605                 reply(f, 550, "Directory change failed; permission denied.");
606             }
607         }
608     } else {
609         reply(f, 550, "Directory change failed; directory does not exist.");
610     }
611
612     /* if everything is okay, change into the directory */
613     if (dir_okay) {
614         daemon_assert(strlen(target) < sizeof(f->dir));
615         /* send a readme unless we changed to our current directory */
616         if (strcmp(f->dir, target) != 0) {
617             strcpy(f->dir, target);
618             send_readme(f, 250);
619         } else {
620             strcpy(f->dir, target);
621         }
622         reply(f, 250, "Directory change successful.");
623     }
624
625     daemon_assert(invariant(f));
626 }
627
628 static void do_quit(ftp_session_t *f, const ftp_command_t *cmd) 
629 {
630     daemon_assert(invariant(f));
631     daemon_assert(cmd != NULL);
632     daemon_assert(cmd->num_arg == 0);
633
634     reply(f, 221, "Service closing control connection.");
635     f->session_active = 0;
636
637     daemon_assert(invariant(f));
638 }
639
640 /* support for the various port setting functions */
641 static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port)
642 {
643     daemon_assert(invariant(f));
644     daemon_assert(host_port != NULL);
645
646     if (f->epsv_all_set) {
647         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
648     } else if (!ip_equal(&f->client_addr, host_port)) {
649         reply(f, 500, "Port must be on command channel IP.");
650     } else if (ntohs(SINPORT(host_port)) < IPPORT_RESERVED) {
651         reply(f, 500, "Port may not be less than 1024, which is reserved.");
652     } else {
653         /* close any outstanding PASSIVE port */
654         if (f->data_channel == DATA_PASSIVE) {
655             close(f->server_fd);
656             f->server_fd = -1;
657         }
658
659         f->data_channel = DATA_PORT;
660         f->data_port = *host_port;
661         reply(f, 200, "Command okay.");
662     }
663
664     daemon_assert(invariant(f));
665 }
666
667 /* set IP and port for client to receive data on */
668 static void do_port(ftp_session_t *f, const ftp_command_t *cmd) 
669 {
670     const sockaddr_storage_t *host_port;
671
672     daemon_assert(invariant(f));
673     daemon_assert(cmd != NULL);
674     daemon_assert(cmd->num_arg == 1);
675
676     host_port = &cmd->arg[0].host_port;
677     daemon_assert(SSFAM(host_port) == AF_INET);
678
679     set_port(f, host_port);
680
681     daemon_assert(invariant(f));
682 }
683
684 /* set IP and port for client to receive data on, transport independent */
685 static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd)  
686 {
687     const sockaddr_storage_t *host_port;
688
689     daemon_assert(invariant(f));
690     daemon_assert(cmd != NULL);
691     daemon_assert(cmd->num_arg == 1);                                   
692
693     host_port = &cmd->arg[0].host_port;
694
695 #ifdef INET6
696     if ((SSFAM(host_port) != AF_INET) && (SSFAM(host_port) != AF_INET6)) {
697         reply(f, 521, "Only IPv4 and IPv6 supported, address families (4,6)");
698     }
699 #else
700     if (SSFAM(host_port) != AF_INET) {
701         reply(f, 521, "Only IPv4 supported, address family (4)");
702     }
703 #endif
704     else
705        set_port(f, host_port);
706
707     daemon_assert(invariant(f));
708 }
709
710 /* set IP and port for the client to receive data on, IPv6 extension */
711 /*                                                                   */
712 /* RFC 2428 specifies that if the data connection is going to be on  */
713 /* the same IP as the control connection, EPSV must be used.  Since  */
714 /* that is the only mode of transfer we support, we reject all EPRT  */
715 /* requests.                                                         */
716 static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd)  
717 {
718     daemon_assert(invariant(f));
719     daemon_assert(cmd != NULL);
720     daemon_assert(cmd->num_arg == 1);                                   
721
722     reply(f, 500, "EPRT not supported, use EPSV.");
723
724     daemon_assert(invariant(f));
725 }
726
727 /* support for the various pasv setting functions */
728 /* returns the file descriptor of the bound port, or -1 on error */
729 /* note: the "bind_addr" parameter will be modified, having its port set */
730 static int set_pasv(ftp_session_t *f, sockaddr_storage_t *bind_addr)
731 {
732     int socket_fd;
733     int port;
734
735     daemon_assert(invariant(f));
736     daemon_assert(bind_addr != NULL);
737
738     socket_fd = socket(SSFAM(bind_addr), SOCK_STREAM, 0);
739     if (socket_fd == -1) {
740         char errbuf[ERRBUF_SIZE];
741         reply(f, 500, "Error creating server socket; %s.",
742               strerror_r(errno, errbuf, ERRBUF_SIZE));
743         return -1;
744     } 
745
746     for (;;) {
747         port = get_passive_port();
748         SINPORT(bind_addr) = htons(port);
749         if (bind(socket_fd, (struct sockaddr *)bind_addr, 
750             sizeof(struct sockaddr)) == 0) 
751         {
752             break;
753         }
754         if (errno != EADDRINUSE) {
755             char errbuf[ERRBUF_SIZE];
756             /* Note that reply() may enable cancellation.  */
757             close(socket_fd);
758             reply(f, 500, "Error binding server port; %s.",
759                   strerror_r(errno, errbuf, ERRBUF_SIZE));
760             return -1;
761         }
762     }
763
764     if (listen(socket_fd, 1) != 0) {
765         char errbuf[ERRBUF_SIZE];
766         /* Note that reply() may enable cancellation.  */
767         close(socket_fd);
768         reply(f, 500, "Error listening on server port; %s.",
769               strerror_r(errno, errbuf, ERRBUF_SIZE));
770         return -1;
771     }
772
773     return socket_fd;
774 }
775
776 /* pick a server port to listen for connection on */
777 static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd) 
778 {
779     int socket_fd;
780     unsigned int addr;
781     int port;
782
783     daemon_assert(invariant(f));
784     daemon_assert(cmd != NULL);
785     daemon_assert(cmd->num_arg == 0);
786
787     if (f->epsv_all_set) {
788         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
789         goto exit_pasv;
790     }
791
792     socket_fd = set_pasv(f, &f->server_ipv4_addr);
793     if (socket_fd == -1) {
794         goto exit_pasv;
795     }
796
797     /* report port to client */
798     addr = ntohl(f->server_ipv4_addr.sin_addr.s_addr);
799     port = ntohs(f->server_ipv4_addr.sin_port);
800
801     /* Close any outstanding PASSIVE port.  Do this before invoking
802        reply(), because that may enable cancellation.  */
803     if (f->data_channel == DATA_PASSIVE) {
804       close(f->server_fd);
805     }
806     f->data_channel = DATA_PASSIVE;
807     f->server_fd = socket_fd;
808
809     reply(f, 227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d).",
810         addr >> 24, 
811         (addr >> 16) & 0xff,
812         (addr >> 8)  & 0xff,
813         addr & 0xff,
814         port >> 8, 
815         port & 0xff);
816
817 exit_pasv:
818     daemon_assert(invariant(f));
819 }
820
821 /* pick a server port to listen for connection on, including IPv6 */
822 static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd) 
823 {
824     int socket_fd;
825     char addr[80];
826     uint8_t *a;
827     uint8_t *p;
828
829     daemon_assert(invariant(f));
830     daemon_assert(cmd != NULL);
831     daemon_assert(cmd->num_arg == 0);
832
833     if (f->epsv_all_set) {
834         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
835         goto exit_lpsv;
836     }
837
838     socket_fd = set_pasv(f, &f->server_addr);
839     if (socket_fd == -1) {
840         goto exit_lpsv;
841     }
842
843     /* close any outstanding PASSIVE port */
844     if (f->data_channel == DATA_PASSIVE) {
845       close(f->server_fd);
846     }
847     f->data_channel = DATA_PASSIVE;
848     f->server_fd = socket_fd;
849
850     /* report address and port to client */
851 #ifdef INET6
852     if (SSFAM(&f->server_addr) == AF_INET6) {
853         a = (uint8_t *)&SIN6ADDR(&f->server_addr);
854         p = (uint8_t *)&SIN6PORT(&f->server_addr);
855         snprintf(addr, sizeof(addr),
856             "(6,16,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,2,%d,%d)",
857             a[0],  a[1],  a[2],  a[3],  a[4],  a[5],  a[6],  a[7],  a[8],
858             a[9], a[10], a[11], a[12], a[13], a[14], a[15],  p[0],  p[1]);
859     } else 
860 #endif
861     {
862         a = (uint8_t *)&SIN4ADDR(&f->server_addr);
863         p = (uint8_t *)&SIN4PORT(&f->server_addr);
864         snprintf(addr, sizeof(addr), "(4,4,%d,%d,%d,%d,2,%d,%d)",
865             a[0], a[1], a[2], a[3], p[0], p[1]);    
866     }
867
868     reply(f, 228, "Entering Long Passive Mode %s", addr);
869
870 exit_lpsv:
871     daemon_assert(invariant(f));
872 }
873
874 /* pick a server port to listen for connection on, new IPv6 method */
875 static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd) 
876 {
877     int socket_fd;
878     sockaddr_storage_t *addr;
879
880     daemon_assert(invariant(f));
881     daemon_assert(cmd != NULL);
882     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
883
884     /* check our argument, if any,  and use the appropriate address */
885     if (cmd->num_arg == 0) {
886         addr = &f->server_addr;
887     } else {
888         switch (cmd->arg[0].num) {
889             /* EPSV_ALL is a special number indicating the client sent */
890             /* the command "EPSV ALL" - this is not a request to assign */
891             /* a new passive port, but rather to deny all future port */
892             /* assignment requests other than EPSV */
893             case EPSV_ALL:
894                 f->epsv_all_set = 1;
895                 reply(f, 200, "EPSV ALL command successful.");
896                 goto exit_epsv;
897             case 1:
898                 addr = (sockaddr_storage_t *)&f->server_ipv4_addr;
899                 break;
900 #ifdef INET6
901             case 2:
902                 addr = &f->server_addr;
903                 break;
904             default:
905                 reply(f, 522, "Only IPv4 and IPv6 supported, use (1,2)");
906                 goto exit_epsv;
907 #else
908             default:
909                 reply(f, 522, "Only IPv4 supported, use (1)");
910                 goto exit_epsv;
911 #endif
912         }
913     }
914
915     /* bind port and so on */
916     socket_fd = set_pasv(f, addr);
917     if (socket_fd == -1) {
918         goto exit_epsv;
919     }
920
921     /* close any outstanding PASSIVE port */
922     if (f->data_channel == DATA_PASSIVE) {
923         close(f->server_fd);
924     }
925     f->data_channel = DATA_PASSIVE;
926     f->server_fd = socket_fd;  
927
928     /* report port to client */
929     reply(f, 229, "Entering Extended Passive Mode (|||%d|)", 
930         ntohs(SINPORT(&f->server_addr)));
931
932 exit_epsv:
933     daemon_assert(invariant(f));
934 }
935
936 /* seed the random number generator used to pick a port */
937 static void init_passive_port()
938 {
939     struct timeval tv;
940     unsigned short int seed[3];
941
942     gettimeofday(&tv, NULL);
943     seed[0] = (tv.tv_sec >> 16) & 0xFFFF;
944     seed[1] = tv.tv_sec & 0xFFFF;
945     seed[2] = tv.tv_usec & 0xFFFF;
946     seed48(seed);
947 }
948
949 /* pick a port to try to bind() for passive FTP connections */
950 static int get_passive_port()
951 {
952     static pthread_once_t once_control = PTHREAD_ONCE_INIT;
953     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
954     int port;
955
956     /* initialize the random number generator the first time we're called */
957     pthread_once(&once_control, init_passive_port);
958
959     /* pick a random port between 1024 (pasv_port_low) and 65535
960        (pasv_port_high), inclusive */
961     pthread_mutex_lock(&mutex);
962     port = (lrand48() % (pasv_port_high-pasv_port_low+1)) + pasv_port_low;
963     pthread_mutex_unlock(&mutex);
964
965     daemon_assert (port >= 1024);
966
967     return port;
968 }
969
970 static void do_type(ftp_session_t *f, const ftp_command_t *cmd) 
971 {
972     char type;
973     char form;
974     int cmd_okay;
975
976     daemon_assert(invariant(f));
977     daemon_assert(cmd != NULL);
978     daemon_assert(cmd->num_arg >= 1);
979     daemon_assert(cmd->num_arg <= 2);
980
981     type = cmd->arg[0].string[0];
982     if (cmd->num_arg == 2) {
983         form = cmd->arg[1].string[0];
984     } else {
985         form = 0;
986     }
987
988     cmd_okay = 0;
989     if (type == 'A') {
990         if ((cmd->num_arg == 1) || ((cmd->num_arg == 2) && (form == 'N'))) {
991             f->data_type = TYPE_ASCII;
992             cmd_okay = 1;
993         }
994     } else if (type == 'I') {
995         f->data_type = TYPE_IMAGE;
996         cmd_okay = 1;
997     }
998
999     if (cmd_okay) {
1000         reply(f, 200, "Command okay.");
1001     } else { 
1002         reply(f, 504, "Command not implemented for that parameter.");
1003     }
1004
1005     daemon_assert(invariant(f));
1006 }
1007
1008 static void do_stru(ftp_session_t *f, const ftp_command_t *cmd) 
1009 {
1010     char structure;
1011     int cmd_okay;
1012
1013     daemon_assert(invariant(f));
1014     daemon_assert(cmd != NULL);
1015     daemon_assert(cmd->num_arg == 1);
1016
1017     structure = cmd->arg[0].string[0];
1018     cmd_okay = 0;
1019     if (structure == 'F') {
1020         f->file_structure = STRU_FILE;
1021         cmd_okay = 1;
1022     } else if (structure == 'R') {
1023         f->file_structure = STRU_RECORD;
1024         cmd_okay = 1;
1025     }
1026
1027     if (cmd_okay) {
1028         reply(f, 200, "Command okay.");
1029     } else {
1030         reply(f, 504, "Command not implemented for that parameter.");
1031     }
1032
1033     daemon_assert(invariant(f));
1034 }
1035
1036 static void do_mode(ftp_session_t *f, const ftp_command_t *cmd) 
1037 {
1038     char mode;
1039
1040     daemon_assert(invariant(f));
1041     daemon_assert(cmd != NULL);
1042     daemon_assert(cmd->num_arg == 1);
1043
1044     mode = cmd->arg[0].string[0];
1045     if (mode == 'S') {
1046         reply(f, 200, "Command okay.");
1047     } else {
1048         reply(f, 504, "Command not implemented for that parameter.");
1049     }
1050
1051     daemon_assert(invariant(f));
1052 }
1053
1054 /* convert the user-entered file name into a full path on our local drive */
1055 static void get_absolute_fname(char *fname, 
1056                                int fname_len,
1057                                const char *dir,
1058                                const char *file)
1059 {
1060     daemon_assert(fname != NULL);
1061     daemon_assert(dir != NULL);
1062     daemon_assert(file != NULL);
1063
1064     if (*file == '/') {
1065
1066         /* absolute path, use as input */
1067         daemon_assert(strlen(file) < fname_len);
1068         strcpy(fname, file);
1069
1070     } else {
1071
1072         /* construct a file name based on our current directory */
1073         daemon_assert(strlen(dir) + 1 + strlen(file) < fname_len);
1074         strcpy(fname, dir);
1075
1076         /* add a seperating '/' if we're not at the root */
1077         if (fname[1] != '\0') {
1078             strcat(fname, "/");
1079         }
1080
1081         /* and of course the actual file name */
1082         strcat(fname, file);
1083
1084     }
1085 }
1086
1087 static void do_retr(ftp_session_t *f, const ftp_command_t *cmd) 
1088 {
1089     const char *file_name;
1090     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1091     int file_fd;
1092     struct stat stat_buf;
1093     int socket_fd;
1094     int read_ret;
1095     char buf[4096];
1096     char converted_buf[8192];
1097     int converted_buflen;
1098     struct timeval start_timestamp;
1099     struct timeval end_timestamp;
1100     struct timeval transfer_time;
1101     off_t file_size;
1102     off_t offset;
1103     off_t amt_to_send;
1104     int sendfile_ret;
1105 #ifdef HAVE_FREEBSD_SENDFILE
1106     off_t amt_sent;
1107 #endif
1108     char errbuf[ERRBUF_SIZE];
1109     int state;
1110
1111     daemon_assert(invariant(f));
1112     daemon_assert(cmd != NULL);
1113     daemon_assert(cmd->num_arg == 1);
1114
1115     /* set up for exit */
1116     file_fd = -1;
1117     socket_fd = -1;
1118
1119     pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &state);
1120     pthread_cleanup_push ((void (*)())fd_cleanup, &file_fd);
1121     pthread_cleanup_push ((void (*)())fd_cleanup, &socket_fd);
1122     
1123     /* create an absolute name for our file */
1124     file_name = cmd->arg[0].string;
1125     get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1126     /* open file */
1127     if (!strncmp (full_path, "/dev", 4)
1128         && (!full_path[4] || full_path[4] == '/')) {
1129       file_fd = -1;
1130       errno = ENOENT;
1131     }
1132     else
1133       file_fd = open(full_path, O_RDONLY);
1134     if (file_fd == -1) {
1135         reply(f, 550, "Error opening file; %s.",
1136               strerror_r(errno, errbuf, ERRBUF_SIZE));
1137         goto exit_retr;
1138     }
1139     if (fstat(file_fd, &stat_buf) != 0) {
1140         reply(f, 550, "Error getting file information; %s.",
1141               strerror_r(errno, errbuf, ERRBUF_SIZE));
1142         goto exit_retr;
1143     }
1144 #ifndef STATS_MACRO_BROKEN
1145     if (S_ISDIR(stat_buf.st_mode)) {
1146 #else
1147     if (!S_ISDIR(stat_buf.st_mode)) {
1148 #endif
1149         reply(f, 550, "Error, file is a directory.");
1150         goto exit_retr;
1151     }
1152
1153     /* if the last command was a REST command, restart at the */
1154     /* requested position in the file                         */
1155     if ((f->file_offset_command_number == (f->command_number - 1)) && 
1156         (f->file_offset > 0))
1157     {
1158         if (lseek(file_fd, f->file_offset, SEEK_SET) == -1) {
1159             reply(f, 550, "Error seeking to restart position; %s.", 
1160                 strerror_r(errno, errbuf, ERRBUF_SIZE));
1161             goto exit_retr;
1162         }
1163     }
1164
1165     /* ready to transfer */
1166     reply(f, 150, "About to open data connection.");
1167
1168     /* mark start time */
1169     gettimeofday(&start_timestamp, NULL);
1170
1171     /* open data path */
1172     socket_fd = open_connection(f);
1173     if (socket_fd == -1) {
1174         goto exit_retr;
1175     }
1176
1177     /* we're golden, send the file */
1178     file_size = 0;
1179     if (f->data_type == TYPE_ASCII) {
1180         for (;;) {
1181             read_ret = read(file_fd, buf, sizeof(buf));
1182             if (read_ret == -1) {
1183                 reply(f, 550, "Error reading from file; %s.",
1184                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1185                 goto exit_retr;
1186             }
1187             if (read_ret == 0) {
1188                 break;
1189             }
1190             converted_buflen = convert_newlines(converted_buf, buf, read_ret);
1191
1192             if (write_fully(socket_fd, converted_buf, converted_buflen) == -1) {
1193                 reply(f, 550, "Error writing to data connection; %s.", 
1194                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1195                 goto exit_retr;
1196             }
1197
1198             file_size += converted_buflen;
1199         } 
1200     } else {
1201         daemon_assert(f->data_type == TYPE_IMAGE);
1202         
1203         /* for sendfile(), we still have to use a loop to avoid 
1204            having our watchdog time us out on large files - it does
1205            allow us to avoid an extra copy to/from user space */
1206 #ifdef HAVE_SENDFILE
1207         offset = f->file_offset;
1208         file_size = stat_buf.st_size - offset;
1209         while (offset < stat_buf.st_size) {
1210
1211             amt_to_send = stat_buf.st_size - offset;
1212             if (amt_to_send > 65536) {
1213                 amt_to_send = 65536;
1214             }
1215 #ifdef HAVE_LINUX_SENDFILE
1216             sendfile_ret = sendfile(socket_fd, 
1217                                     file_fd, 
1218                                     &offset, 
1219                                     amt_to_send);
1220             if (sendfile_ret != amt_to_send) {
1221                 reply(f, 550, "Error sending file; %s.",
1222                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1223                 goto exit_retr;
1224             }
1225 #elif HAVE_FREEBSD_SENDFILE
1226             sendfile_ret = sendfile(file_fd, 
1227                                     socket_fd, 
1228                                     offset,
1229                                     amt_to_send,
1230                                     NULL,
1231                                     &amt_sent,
1232                                     0);
1233             if (sendfile_ret != 0) {
1234                 reply(f, 550, "Error sending file; %s.",
1235                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1236                 goto exit_retr;
1237             }
1238             offset += amt_sent;
1239 #endif
1240
1241             watchdog_defer_watched(f->watched);
1242         }
1243 #else
1244         for (;;) {
1245             read_ret = read(file_fd, buf, sizeof(buf));
1246             if (read_ret == -1) {
1247                 reply(f, 550, "Error reading from file; %s.",
1248                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1249                 goto exit_retr;
1250             }
1251             if (read_ret == 0) {
1252                 break;
1253             }
1254             if (write_fully(socket_fd, buf, read_ret) == -1) {
1255                 reply(f, 550, "Error writing to data connection; %s.", 
1256                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1257                 goto exit_retr;
1258             }
1259             file_size += read_ret;
1260
1261             watchdog_defer_watched(f->watched);
1262         }
1263 #endif  /* HAVE_SENDFILE */
1264     }
1265
1266     /* Disconnect eagerly.  */
1267     close(socket_fd);
1268     socket_fd = -1;
1269
1270     /* hey, it worked, let the other side know */
1271     reply(f, 226, "File transfer complete.");
1272
1273     /* mark end time */
1274     gettimeofday(&end_timestamp, NULL);
1275
1276     /* calculate transfer rate */
1277     transfer_time.tv_sec = end_timestamp.tv_sec - start_timestamp.tv_sec;
1278     transfer_time.tv_usec = end_timestamp.tv_usec - start_timestamp.tv_usec;
1279     while (transfer_time.tv_usec >= 1000000) {
1280         transfer_time.tv_sec++;
1281         transfer_time.tv_usec -= 1000000;
1282     }
1283     while (transfer_time.tv_usec < 0) {
1284         transfer_time.tv_sec--;
1285         transfer_time.tv_usec += 1000000;
1286     }
1287
1288     /* note the transfer */
1289     syslog(LOG_INFO, 
1290       "%s retrieved \"%s\", %ld bytes in %ld.%06ld seconds", 
1291       f->client_addr_str, 
1292       full_path,
1293       file_size,
1294       (long int) transfer_time.tv_sec,
1295       (long int) transfer_time.tv_usec);
1296
1297 exit_retr:
1298     /* Close socket_fd.  */
1299     pthread_cleanup_pop (1);
1300     /* Close file_fd.  */
1301     pthread_cleanup_pop (1);
1302     f->file_offset = 0;
1303     pthread_setcancelstate (state, NULL);
1304     daemon_assert(invariant(f));
1305 }
1306
1307 static void do_stor(ftp_session_t *f, const ftp_command_t *cmd) 
1308 {
1309     daemon_assert(invariant(f));
1310     daemon_assert(cmd != NULL);
1311     daemon_assert(cmd->num_arg == 1);
1312
1313     reply(f, 553, "Server will not store files.");
1314
1315     daemon_assert(invariant(f));
1316 }
1317
1318 static int open_connection(ftp_session_t *f)
1319 {
1320     int socket_fd = -1;
1321     struct sockaddr_in addr;
1322     unsigned addr_len;
1323     char errbuf[ERRBUF_SIZE];
1324     int state;
1325
1326     daemon_assert((f->data_channel == DATA_PORT) || 
1327                   (f->data_channel == DATA_PASSIVE));
1328
1329     pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &state);
1330     pthread_cleanup_push ((void (*)())fd_cleanup, &socket_fd);
1331     if (f->data_channel == DATA_PORT) {
1332         socket_fd = socket(SSFAM(&f->data_port), SOCK_STREAM, 0);
1333         if (socket_fd == -1) {
1334             reply(f, 425, "Error creating socket; %s.",
1335                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1336             goto leave_oc;
1337         }
1338         if (connect(socket_fd, (struct sockaddr *)&f->data_port, 
1339                          sizeof(sockaddr_storage_t)) != 0)
1340         {
1341             close(socket_fd);
1342             socket_fd = -1;
1343             reply(f, 425, "Error connecting; %s.",
1344                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1345             goto leave_oc;
1346         }
1347     } else {
1348         daemon_assert(f->data_channel == DATA_PASSIVE);
1349
1350         addr_len = sizeof(struct sockaddr_in);
1351         socket_fd = accept(f->server_fd, (struct sockaddr *)&addr, &addr_len);
1352         if (socket_fd == -1) {
1353             reply(f, 425, "Error accepting connection; %s.",
1354                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1355             goto leave_oc;
1356         }
1357 #ifdef INET6
1358         /* in IPv6, the client can connect to a channel using a different */
1359         /* protocol - in that case, we'll just blindly let the connection */
1360         /* through, otherwise verify addresses match */
1361         if (SAFAM(addr) == SSFAM(&f->client_addr)) {
1362             if (memcmp(&SINADDR(&f->client_addr), &SINADDR(&addr), 
1363                        sizeof(SINADDR(&addr))))
1364             {
1365                 close(socket_fd);
1366                 socket_fd = -1;
1367                 reply(f, 425, 
1368                   "Error accepting connection; connection from invalid IP.");
1369                 goto leave_oc;
1370             }
1371         }
1372 #else
1373         if (memcmp(&f->client_addr.sin_addr, 
1374             &addr.sin_addr, sizeof(struct in_addr))) 
1375         {
1376             close(socket_fd);
1377             socket_fd = -1;
1378             reply(f, 425, 
1379               "Error accepting connection; connection from invalid IP.");
1380             goto leave_oc;
1381         }
1382 #endif
1383     }
1384
1385  leave_oc:
1386     /* Do not close SOCKET_FD here.  */
1387     pthread_cleanup_pop (0);
1388     pthread_setcancelstate (state, NULL);
1389
1390     return socket_fd;
1391 }
1392
1393 /* convert any '\n' to '\r\n' */
1394 /* destination should be twice the size of the source for safety */
1395 static int convert_newlines(char *dst, const char *src, int srclen)
1396 {
1397     int i;
1398     int dstlen;
1399
1400     daemon_assert(dst != NULL);
1401     daemon_assert(src != NULL);
1402
1403     dstlen = 0;
1404     for (i=0; i<srclen; i++) {
1405         if (src[i] == '\n') {
1406             dst[dstlen++] = '\r';
1407         }
1408         dst[dstlen++] = src[i];
1409     }
1410     return dstlen;
1411 }
1412
1413 static int write_fully(int fd, const char *buf, int buflen)
1414 {
1415     int amt_written;
1416     int write_ret;
1417
1418     amt_written = 0;
1419     while (amt_written < buflen) {
1420         write_ret = write(fd, buf+amt_written, buflen-amt_written);
1421         if (write_ret <= 0) {
1422             return -1;
1423         }
1424         amt_written += write_ret;
1425     }
1426     return amt_written;
1427 }
1428
1429 static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd) 
1430 {
1431     daemon_assert(invariant(f));
1432     daemon_assert(cmd != NULL);
1433     daemon_assert(cmd->num_arg == 0);
1434
1435     reply(f, 257, "\"%s\" is current directory.", f->dir);
1436
1437     daemon_assert(invariant(f));
1438 }
1439
1440 #if 0
1441 /* 
1442   because oftpd uses glob(), it is possible for users to launch a 
1443   denial-of-service attack by sending certain wildcard expressions that
1444   create extremely large lists of files, e.g. "*/../*/../*/../*/../*/../*"
1445
1446   in order to prevent this, a user may pass wildcards, or paths, but not
1447   both as arguments to LIST or NLST - at most all the files from a single 
1448   directory will be returned
1449 */
1450 #endif
1451
1452 /* check if a filespec has a wildcard in it */
1453 static int filespec_has_wildcard(const char *filespec)
1454 {
1455     daemon_assert(filespec != NULL);
1456
1457     /* check each character for wildcard */
1458     while (*filespec != '\0') {
1459         switch (*filespec) {
1460             /* wildcards */
1461             case '*':
1462             case '?':
1463             case '[':
1464                 return 1;
1465
1466             /* backslash escapes next character unless at end of string */
1467             case '\\':
1468                 if (*(filespec+1) != '\0') {
1469                     filespec++;
1470                 }
1471                 break;
1472         }
1473         filespec++;
1474     }
1475
1476     return 0;
1477 }
1478
1479 /* filespec includes path separator, i.e. '/' */
1480 static int filespec_has_path_separator(const char *filespec)
1481 {
1482     daemon_assert(filespec != NULL);
1483
1484     /* check each character for path separator */
1485     if (strchr(filespec, '/') != NULL) {
1486         return 1;
1487     } else {
1488         return 0;
1489     }
1490 }
1491
1492 /* returns whether filespec is legal or not */
1493 static int filespec_is_legal(const char *filespec)
1494 {
1495     daemon_assert(filespec != NULL);
1496
1497     if (filespec_has_wildcard(filespec)) {
1498         if (filespec_has_path_separator(filespec)) {
1499             return 0;
1500         }
1501     }
1502     return 1;
1503 }
1504
1505 static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd) 
1506 {
1507     int fd;
1508     const char *param;
1509     int send_ok;
1510
1511     daemon_assert(invariant(f));
1512     daemon_assert(cmd != NULL);
1513     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
1514
1515     /* figure out what parameters to use */
1516     if (cmd->num_arg == 0) {
1517         param = "*";
1518     } else {
1519         daemon_assert(cmd->num_arg == 1);
1520
1521         /* ignore attempts to send options to "ls" by silently dropping */
1522         if (cmd->arg[0].string[0] == '-') {
1523             param = "*";
1524         } else {
1525             param = cmd->arg[0].string;
1526         }
1527     }
1528
1529     /* check spec passed */
1530     if (!filespec_is_legal(param)) {
1531         reply(f, 550, "Illegal filename passed.");
1532         return;
1533     }
1534
1535     /* ready to list */
1536     reply(f, 150, "About to send name list.");
1537
1538     /* open our data connection */
1539     fd = open_connection(f);
1540     if (fd == -1)
1541       return;
1542
1543     pthread_cleanup_push ((void (*)())fd_cleanup, &fd);
1544
1545     /* send any files */
1546     send_ok = file_nlst(fd, f->dir, param);
1547
1548     /* strange handshake for Netscape's benefit */
1549     netscape_hack(fd);
1550
1551     if (send_ok) {
1552         reply(f, 226, "Transfer complete.");
1553     } else {
1554         reply(f, 451, "Error sending name list.");
1555     }
1556
1557     pthread_cleanup_pop (1);
1558
1559     daemon_assert(invariant(f));
1560 }
1561
1562 static void do_list(ftp_session_t *f, const ftp_command_t *cmd) 
1563 {
1564     int fd;
1565     const char *param;
1566     int send_ok;
1567
1568     daemon_assert(invariant(f));
1569     daemon_assert(cmd != NULL);
1570     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
1571
1572     /* figure out what parameters to use */
1573     if (cmd->num_arg == 0) {
1574         param = "*";
1575     } else {
1576         daemon_assert(cmd->num_arg == 1);
1577
1578         /* ignore attempts to send options to "ls" by silently dropping */
1579         if (cmd->arg[0].string[0] == '-') {
1580             param = "*";
1581         } else {
1582             param = cmd->arg[0].string;
1583         }
1584     }
1585
1586     /* check spec passed */
1587     if (!filespec_is_legal(param)) {
1588         reply(f, 550, "Illegal filename passed.");
1589         return;
1590     }
1591
1592     /* ready to list */
1593     reply(f, 150, "About to send file list.");
1594
1595     /* open our data connection */
1596     fd = open_connection(f);
1597     if (fd == -1)
1598       return;
1599
1600     pthread_cleanup_push ((void (*)())fd_cleanup, &fd);
1601
1602     /* send any files */
1603     send_ok = file_list(fd, f->dir, param);
1604
1605     /* strange handshake for Netscape's benefit */
1606     netscape_hack(fd);
1607
1608     if (send_ok) {
1609         reply(f, 226, "Transfer complete.");
1610     } else {
1611         reply(f, 451, "Error sending file list.");
1612     }
1613
1614     pthread_cleanup_pop (1);
1615
1616     daemon_assert(invariant(f));
1617 }
1618
1619 static void do_syst(ftp_session_t *f, const ftp_command_t *cmd) 
1620 {
1621     daemon_assert(invariant(f));
1622     daemon_assert(cmd != NULL);
1623     daemon_assert(cmd->num_arg == 0);
1624
1625     reply(f, 215, "UNIX");
1626
1627     daemon_assert(invariant(f));
1628 }
1629
1630
1631 static void do_noop(ftp_session_t *f, const ftp_command_t *cmd) 
1632 {
1633     daemon_assert(invariant(f));
1634     daemon_assert(cmd != NULL);
1635     daemon_assert(cmd->num_arg == 0);
1636
1637     reply(f, 200, "Command okay.");
1638
1639     daemon_assert(invariant(f));
1640 }
1641
1642 static void do_rest(ftp_session_t *f, const ftp_command_t *cmd) 
1643 {
1644     daemon_assert(invariant(f));
1645     daemon_assert(cmd != NULL);
1646     daemon_assert(cmd->num_arg == 1);
1647
1648     if (f->data_type != TYPE_IMAGE) {
1649         reply(f, 555, "Restart not possible in ASCII mode.");
1650     } else if (f->file_structure != STRU_FILE) {
1651         reply(f, 555, "Restart only possible with FILE structure.");
1652     } else {
1653         f->file_offset = cmd->arg[0].offset;
1654         f->file_offset_command_number = f->command_number;
1655         reply(f, 350, "Restart okay, awaiting file retrieval request.");
1656     }
1657
1658     daemon_assert(invariant(f));
1659 }
1660
1661 static void do_size(ftp_session_t *f, const ftp_command_t *cmd) 
1662 {
1663     const char *file_name;
1664     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1665     struct stat stat_buf;
1666     char errbuf[ERRBUF_SIZE];
1667     
1668     daemon_assert(invariant(f));
1669     daemon_assert(cmd != NULL);
1670     daemon_assert(cmd->num_arg == 1);
1671
1672     if (f->data_type != TYPE_IMAGE) {
1673         reply(f, 550, "Size cannot be determined in ASCII mode.");
1674     } else if (f->file_structure != STRU_FILE) {
1675         reply(f, 550, "Size cannot be determined with FILE structure.");
1676     } else {
1677
1678         /* create an absolute name for our file */
1679         file_name = cmd->arg[0].string;
1680         get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1681         /* get the file information */
1682         if (!strncmp (full_path, "/dev", 4)
1683             && (!full_path[4] || full_path[4] == '/')) {
1684             errno = ENOENT;
1685             reply(f, 550, "Error getting file status; %s.",
1686                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1687         }
1688         else if (stat(full_path, &stat_buf) != 0) {
1689             reply(f, 550, "Error getting file status; %s.",
1690                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1691         }
1692         else if (S_ISDIR(stat_buf.st_mode)) {
1693             reply(f, 550, "File is a directory, SIZE command not valid.");
1694         } else {
1695             /* output the size */
1696             if (sizeof(off_t) == 8) {
1697                 reply(f, 213, "%llu", stat_buf.st_size);
1698             } else {
1699                 reply(f, 213, "%lu", stat_buf.st_size);
1700             } 
1701         }
1702
1703     }
1704
1705     daemon_assert(invariant(f));
1706 }
1707
1708 /* if no gmtime_r() is available, provide one */
1709 #ifndef HAVE_GMTIME_R
1710 struct tm *gmtime_r(const time_t *timep, struct tm *timeptr) 
1711 {
1712     static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
1713
1714     pthread_mutex_lock(&time_lock);
1715     *timeptr = *(gmtime(timep));
1716     pthread_mutex_unlock(&time_lock);
1717     return timeptr;
1718 }
1719 #endif /* HAVE_GMTIME_R */
1720
1721 static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd) 
1722 {
1723     const char *file_name;
1724     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1725     struct stat stat_buf;
1726     struct tm mtime;
1727     char time_buf[16];
1728     char errbuf[ERRBUF_SIZE];
1729     
1730     daemon_assert(invariant(f));
1731     daemon_assert(cmd != NULL);
1732     daemon_assert(cmd->num_arg == 1);
1733
1734     /* create an absolute name for our file */
1735     file_name = cmd->arg[0].string;
1736     get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1737
1738     /* get the file information */
1739     if (!strncmp (full_path, "/dev", 4)
1740         && (!full_path[4] || full_path[4] == '/')) {
1741         errno = ENOENT;
1742         reply(f, 550, "Error getting file status; %s.",
1743               strerror_r(errno, errbuf, ERRBUF_SIZE));
1744     }
1745     else if (stat(full_path, &stat_buf) != 0) {
1746         reply(f, 550, "Error getting file status; %s.",
1747               strerror_r(errno, errbuf, ERRBUF_SIZE));
1748     } else {
1749         gmtime_r(&stat_buf.st_mtime, &mtime);
1750         strftime(time_buf, sizeof(time_buf), "%Y%m%d%H%M%S", &mtime);
1751         reply(f, 213, time_buf);
1752     }
1753
1754     daemon_assert(invariant(f));
1755 }
1756
1757
1758 static void send_readme(const ftp_session_t *f, int code)
1759 {
1760     char file_name[PATH_MAX+1];
1761     int dir_len;
1762     struct stat stat_buf;
1763     int fd;
1764     int read_ret;
1765     char buf[4096];
1766     char code_str[8];
1767     char *p;
1768     int len;
1769     char *nl;
1770     int line_len;
1771
1772     daemon_assert(invariant(f));
1773     daemon_assert(code >= 100);
1774     daemon_assert(code <= 559);
1775
1776     /* verify our README wouldn't be too long */
1777     dir_len = strlen(f->dir);
1778     if ((dir_len + 1 + sizeof(README_FILE_NAME)) > sizeof(file_name))
1779       return;
1780     
1781     /* create a README file name */
1782     strcpy(file_name, f->dir);
1783     strcat(file_name, "/");
1784     strcat(file_name, README_FILE_NAME);
1785
1786     /* open our file */
1787     fd = open(file_name, O_RDONLY);
1788     if (fd == -1)
1789       return;
1790
1791     pthread_cleanup_push ((void (*)())fd_cleanup, &fd);
1792
1793     /* verify this isn't a directory */
1794     if (fstat(fd, &stat_buf) != 0) {
1795         goto exit_send_readme;
1796     }
1797 #ifndef STATS_MACRO_BROKEN
1798     if (S_ISDIR(stat_buf.st_mode)) {
1799 #else
1800     if (!S_ISDIR(stat_buf.st_mode)) {
1801 #endif
1802         goto exit_send_readme;
1803     }
1804
1805     /* convert our code to a buffer */
1806     daemon_assert(code >= 100);
1807     daemon_assert(code <= 999);
1808     sprintf(code_str, "%03d-", code);
1809
1810     /* read and send */
1811     read_ret = read(fd, buf, sizeof(buf));
1812     if (read_ret > 0) {
1813         telnet_session_print(f->telnet_session, code_str);
1814         while (read_ret > 0) {
1815             p = buf;
1816             len = read_ret;
1817             nl = memchr(p, '\n', len);
1818             while ((len > 0) && (nl != NULL)) {
1819                 *nl = '\0';
1820                 telnet_session_println(f->telnet_session, p);
1821                 line_len = nl - p;
1822                 len -= line_len + 1;
1823                 if (len > 0) {
1824                     telnet_session_print(f->telnet_session, code_str);
1825                 }
1826                 p = nl+1;
1827                 nl = memchr(p, '\n', len);
1828             }
1829             if (len > 0) {
1830                 telnet_session_print(f->telnet_session, p);
1831             }
1832
1833             read_ret = read(fd, buf, sizeof(buf));
1834         }
1835     }
1836
1837     /* cleanup and exit */
1838 exit_send_readme:
1839     pthread_cleanup_pop (1);
1840
1841     daemon_assert(invariant(f));
1842 }
1843
1844 /* hack which prevents Netscape error in file list */
1845 static void netscape_hack(int fd)
1846 {
1847     fd_set readfds;
1848     struct timeval timeout;
1849     int select_ret;
1850     char c;
1851     int state;
1852
1853     daemon_assert(fd >= 0);
1854
1855     pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &state);
1856     shutdown(fd, 1);
1857     FD_ZERO(&readfds);
1858     FD_SET(fd, &readfds);
1859     timeout.tv_sec = 15;
1860     timeout.tv_usec = 0;
1861     select_ret = select(fd+1, &readfds, NULL, NULL, &timeout);
1862     if (select_ret > 0) {
1863         read(fd, &c, 1);
1864     }
1865     pthread_setcancelstate (state, NULL);
1866 }
1867
1868 /* compare two addresses to see if they contain the same IP address */
1869 static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b)
1870 {
1871     daemon_assert(a != NULL);
1872     daemon_assert(b != NULL);
1873     daemon_assert((SSFAM(a) == AF_INET) || (SSFAM(a) == AF_INET6));
1874     daemon_assert((SSFAM(b) == AF_INET) || (SSFAM(b) == AF_INET6));
1875
1876     if (SSFAM(a) != SSFAM(b)) {
1877         return 0;
1878     }
1879     if (memcmp(&SINADDR(a), &SINADDR(b), sizeof(SINADDR(a))) != 0) {
1880         return 0;
1881     }
1882     return 1;
1883 }
1884