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