Lets keep our version of opftpd in the CVS
[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 */
573     dir_okay = 0;
574     if (stat(target, &stat_buf) == 0) {
575 #ifndef STAT_MACROS_BROKEN
576         if (!S_ISDIR(stat_buf.st_mode)) {
577 #else
578         if (S_ISDIR(stat_buf.st_mode)) {
579 #endif
580             reply(f, 550,"Directory change failed; target is not a directory.");
581         } else { 
582             if (S_IXOTH & stat_buf.st_mode) {
583                 dir_okay = 1;
584             } else if ((stat_buf.st_gid == getegid()) && 
585                 (S_IXGRP & stat_buf.st_mode)) 
586             {
587                 dir_okay = 1;
588             } else if ((stat_buf.st_uid == geteuid()) && 
589                 (S_IXUSR & stat_buf.st_mode)) 
590             {
591                 dir_okay = 1;
592             } else {
593                 reply(f, 550, "Directory change failed; permission denied.");
594             }
595         }
596     } else {
597         reply(f, 550, "Directory change failed; directory does not exist.");
598     }
599
600     /* if everything is okay, change into the directory */
601     if (dir_okay) {
602         daemon_assert(strlen(target) < sizeof(f->dir));
603         /* send a readme unless we changed to our current directory */
604         if (strcmp(f->dir, target) != 0) {
605             strcpy(f->dir, target);
606             send_readme(f, 250);
607         } else {
608             strcpy(f->dir, target);
609         }
610         reply(f, 250, "Directory change successful.");
611     }
612
613     daemon_assert(invariant(f));
614 }
615
616 static void do_quit(ftp_session_t *f, const ftp_command_t *cmd) 
617 {
618     daemon_assert(invariant(f));
619     daemon_assert(cmd != NULL);
620     daemon_assert(cmd->num_arg == 0);
621
622     reply(f, 221, "Service closing control connection.");
623     f->session_active = 0;
624
625     daemon_assert(invariant(f));
626 }
627
628 /* support for the various port setting functions */
629 static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port)
630 {
631     daemon_assert(invariant(f));
632     daemon_assert(host_port != NULL);
633
634     if (f->epsv_all_set) {
635         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
636     } else if (!ip_equal(&f->client_addr, host_port)) {
637         reply(f, 500, "Port must be on command channel IP.");
638     } else if (ntohs(SINPORT(host_port)) < IPPORT_RESERVED) {
639         reply(f, 500, "Port may not be less than 1024, which is reserved.");
640     } else {
641         /* close any outstanding PASSIVE port */
642         if (f->data_channel == DATA_PASSIVE) {
643             close(f->server_fd);
644             f->server_fd = -1;
645         }
646
647         f->data_channel = DATA_PORT;
648         f->data_port = *host_port;
649         reply(f, 200, "Command okay.");
650     }
651
652     daemon_assert(invariant(f));
653 }
654
655 /* set IP and port for client to receive data on */
656 static void do_port(ftp_session_t *f, const ftp_command_t *cmd) 
657 {
658     const sockaddr_storage_t *host_port;
659
660     daemon_assert(invariant(f));
661     daemon_assert(cmd != NULL);
662     daemon_assert(cmd->num_arg == 1);
663
664     host_port = &cmd->arg[0].host_port;
665     daemon_assert(SSFAM(host_port) == AF_INET);
666
667     set_port(f, host_port);
668
669     daemon_assert(invariant(f));
670 }
671
672 /* set IP and port for client to receive data on, transport independent */
673 static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd)  
674 {
675     const sockaddr_storage_t *host_port;
676
677     daemon_assert(invariant(f));
678     daemon_assert(cmd != NULL);
679     daemon_assert(cmd->num_arg == 1);                                   
680
681     host_port = &cmd->arg[0].host_port;
682
683 #ifdef INET6
684     if ((SSFAM(host_port) != AF_INET) && (SSFAM(host_port) != AF_INET6)) {
685         reply(f, 521, "Only IPv4 and IPv6 supported, address families (4,6)");
686     }
687 #else
688     if (SSFAM(host_port) != AF_INET) {
689         reply(f, 521, "Only IPv4 supported, address family (4)");
690     }
691 #endif
692     else
693        set_port(f, host_port);
694
695     daemon_assert(invariant(f));
696 }
697
698 /* set IP and port for the client to receive data on, IPv6 extension */
699 /*                                                                   */
700 /* RFC 2428 specifies that if the data connection is going to be on  */
701 /* the same IP as the control connection, EPSV must be used.  Since  */
702 /* that is the only mode of transfer we support, we reject all EPRT  */
703 /* requests.                                                         */
704 static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd)  
705 {
706     const sockaddr_storage_t *host_port;
707
708     daemon_assert(invariant(f));
709     daemon_assert(cmd != NULL);
710     daemon_assert(cmd->num_arg == 1);                                   
711
712     reply(f, 500, "EPRT not supported, use EPSV.");
713
714     daemon_assert(invariant(f));
715 }
716
717 /* support for the various pasv setting functions */
718 /* returns the file descriptor of the bound port, or -1 on error */
719 /* note: the "host_port" parameter will be modified, having its port set */
720 static int set_pasv(ftp_session_t *f, sockaddr_storage_t *bind_addr)
721 {
722     int socket_fd;
723     int port;
724
725     daemon_assert(invariant(f));
726     daemon_assert(bind_addr != NULL);
727
728     socket_fd = socket(SSFAM(bind_addr), SOCK_STREAM, 0);
729     reopen_syslog_hack (socket_fd);
730     if (socket_fd == -1) {
731         reply(f, 500, "Error creating server socket; %s.", strerror(errno));
732         return -1;
733     } 
734
735     for (;;) {
736         port = get_passive_port();
737         SINPORT(bind_addr) = htons(port);
738         if (bind(socket_fd, (struct sockaddr *)bind_addr, 
739             sizeof(struct sockaddr)) == 0) 
740         {
741             break;
742         }
743         if (errno != EADDRINUSE) {
744             reply(f, 500, "Error binding server port; %s.", strerror(errno));
745             close(socket_fd);
746             return -1;
747         }
748     }
749
750     if (listen(socket_fd, 1) != 0) {
751         reply(f, 500, "Error listening on server port; %s.", strerror(errno));
752         close(socket_fd);
753         return -1;
754     }
755
756     return socket_fd;
757 }
758
759 /* pick a server port to listen for connection on */
760 static void do_pasv(ftp_session_t *f, const ftp_command_t *cmd) 
761 {
762     int socket_fd;
763     unsigned int addr;
764     int port;
765
766     daemon_assert(invariant(f));
767     daemon_assert(cmd != NULL);
768     daemon_assert(cmd->num_arg == 0);
769
770     if (f->epsv_all_set) {
771         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
772         goto exit_pasv;
773     }
774
775     socket_fd = set_pasv(f, &f->server_ipv4_addr);
776     if (socket_fd == -1) {
777         goto exit_pasv;
778     }
779
780     /* report port to client */
781     addr = ntohl(f->server_ipv4_addr.sin_addr.s_addr);
782     port = ntohs(f->server_ipv4_addr.sin_port);
783     reply(f, 227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d).",
784         addr >> 24, 
785         (addr >> 16) & 0xff,
786         (addr >> 8)  & 0xff,
787         addr & 0xff,
788         port >> 8, 
789         port & 0xff);
790
791    /* close any outstanding PASSIVE port */
792    if (f->data_channel == DATA_PASSIVE) {
793        close(f->server_fd);
794    }
795    f->data_channel = DATA_PASSIVE;
796    f->server_fd = socket_fd;
797
798 exit_pasv:
799     daemon_assert(invariant(f));
800 }
801
802 /* pick a server port to listen for connection on, including IPv6 */
803 static void do_lpsv(ftp_session_t *f, const ftp_command_t *cmd) 
804 {
805     int socket_fd;
806     char addr[80];
807     uint8_t *a;
808     uint8_t *p;
809
810     daemon_assert(invariant(f));
811     daemon_assert(cmd != NULL);
812     daemon_assert(cmd->num_arg == 0);
813
814     if (f->epsv_all_set) {
815         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
816         goto exit_lpsv;
817     }
818
819     socket_fd = set_pasv(f, &f->server_addr);
820     if (socket_fd == -1) {
821         goto exit_lpsv;
822     }
823
824     /* report address and port to client */
825 #ifdef INET6
826     if (SSFAM(&f->server_addr) == AF_INET6) {
827         a = (uint8_t *)&SIN6ADDR(&f->server_addr);
828         p = (uint8_t *)&SIN6PORT(&f->server_addr);
829         snprintf(addr, sizeof(addr),
830             "(6,16,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,2,%d,%d)",
831             a[0],  a[1],  a[2],  a[3],  a[4],  a[5],  a[6],  a[7],  a[8],
832             a[9], a[10], a[11], a[12], a[13], a[14], a[15],  p[0],  p[1]);
833     } else 
834 #endif
835     {
836         a = (uint8_t *)&SIN4ADDR(&f->server_addr);
837         p = (uint8_t *)&SIN4PORT(&f->server_addr);
838         snprintf(addr, sizeof(addr), "(4,4,%d,%d,%d,%d,2,%d,%d)",
839             a[0], a[1], a[2], a[3], p[0], p[1]);    
840     }
841
842     reply(f, 228, "Entering Long Passive Mode %s", addr);
843
844    /* close any outstanding PASSIVE port */
845    if (f->data_channel == DATA_PASSIVE) {
846        close(f->server_fd);
847    }
848    f->data_channel = DATA_PASSIVE;
849    f->server_fd = socket_fd;
850
851 exit_lpsv:
852     daemon_assert(invariant(f));
853 }
854
855 /* pick a server port to listen for connection on, new IPv6 method */
856 static void do_epsv(ftp_session_t *f, const ftp_command_t *cmd) 
857 {
858     int socket_fd;
859     sockaddr_storage_t *addr;
860
861     daemon_assert(invariant(f));
862     daemon_assert(cmd != NULL);
863     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
864
865     /* check our argument, if any,  and use the appropriate address */
866     if (cmd->num_arg == 0) {
867         addr = &f->server_addr;
868     } else {
869         switch (cmd->arg[0].num) {
870             /* EPSV_ALL is a special number indicating the client sent */
871             /* the command "EPSV ALL" - this is not a request to assign */
872             /* a new passive port, but rather to deny all future port */
873             /* assignment requests other than EPSV */
874             case EPSV_ALL:
875                 f->epsv_all_set = 1;
876                 reply(f, 200, "EPSV ALL command successful.");
877                 goto exit_epsv;
878             case 1:
879                 addr = (sockaddr_storage_t *)&f->server_ipv4_addr;
880                 break;
881 #ifdef INET6
882             case 2:
883                 addr = &f->server_addr;
884                 break;
885             default:
886                 reply(f, 522, "Only IPv4 and IPv6 supported, use (1,2)");
887                 goto exit_epsv;
888 #else
889             default:
890                 reply(f, 522, "Only IPv4 supported, use (1)");
891                 goto exit_epsv;
892 #endif
893         }
894     }
895
896     /* bind port and so on */
897     socket_fd = set_pasv(f, addr);
898     if (socket_fd == -1) {
899         goto exit_epsv;
900     }
901
902     /* report port to client */
903     reply(f, 229, "Entering Extended Passive Mode (|||%d|)", 
904         ntohs(SINPORT(&f->server_addr)));
905
906     /* close any outstanding PASSIVE port */
907     if (f->data_channel == DATA_PASSIVE) {
908         close(f->server_fd);
909     }
910     f->data_channel = DATA_PASSIVE;
911     f->server_fd = socket_fd;  
912
913 exit_epsv:
914     daemon_assert(invariant(f));
915 }
916
917 /* seed the random number generator used to pick a port */
918 static void init_passive_port()
919 {
920     struct timeval tv;
921     unsigned short int seed[3];
922
923     gettimeofday(&tv, NULL);
924     seed[0] = (tv.tv_sec >> 16) & 0xFFFF;
925     seed[1] = tv.tv_sec & 0xFFFF;
926     seed[2] = tv.tv_usec & 0xFFFF;
927     seed48(seed);
928 }
929
930 /* pick a port to try to bind() for passive FTP connections */
931 static int get_passive_port()
932 {
933     static pthread_once_t once_control = PTHREAD_ONCE_INIT;
934     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
935     int port;
936
937     /* initialize the random number generator the first time we're called */
938     pthread_once(&once_control, init_passive_port);
939
940     /* pick a random port between 1024 (pasv_port_low) and 65535
941        (pasv_port_high), inclusive */
942     pthread_mutex_lock(&mutex);
943     port = (lrand48() % (pasv_port_high-pasv_port_low+1)) + pasv_port_low;
944     pthread_mutex_unlock(&mutex);
945
946     daemon_assert (port >= 1024);
947
948     return port;
949 }
950
951 static void do_type(ftp_session_t *f, const ftp_command_t *cmd) 
952 {
953     char type;
954     char form;
955     int cmd_okay;
956
957     daemon_assert(invariant(f));
958     daemon_assert(cmd != NULL);
959     daemon_assert(cmd->num_arg >= 1);
960     daemon_assert(cmd->num_arg <= 2);
961
962     type = cmd->arg[0].string[0];
963     if (cmd->num_arg == 2) {
964         form = cmd->arg[1].string[0];
965     } else {
966         form = 0;
967     }
968
969     cmd_okay = 0;
970     if (type == 'A') {
971         if ((cmd->num_arg == 1) || ((cmd->num_arg == 2) && (form == 'N'))) {
972             f->data_type = TYPE_ASCII;
973             cmd_okay = 1;
974         }
975     } else if (type == 'I') {
976         f->data_type = TYPE_IMAGE;
977         cmd_okay = 1;
978     }
979
980     if (cmd_okay) {
981         reply(f, 200, "Command okay.");
982     } else { 
983         reply(f, 504, "Command not implemented for that parameter.");
984     }
985
986     daemon_assert(invariant(f));
987 }
988
989 static void do_stru(ftp_session_t *f, const ftp_command_t *cmd) 
990 {
991     char structure;
992     int cmd_okay;
993
994     daemon_assert(invariant(f));
995     daemon_assert(cmd != NULL);
996     daemon_assert(cmd->num_arg == 1);
997
998     structure = cmd->arg[0].string[0];
999     cmd_okay = 0;
1000     if (structure == 'F') {
1001         f->file_structure = STRU_FILE;
1002         cmd_okay = 1;
1003     } else if (structure == 'R') {
1004         f->file_structure = STRU_RECORD;
1005         cmd_okay = 1;
1006     }
1007
1008     if (cmd_okay) {
1009         reply(f, 200, "Command okay.");
1010     } else {
1011         reply(f, 504, "Command not implemented for that parameter.");
1012     }
1013
1014     daemon_assert(invariant(f));
1015 }
1016
1017 static void do_mode(ftp_session_t *f, const ftp_command_t *cmd) 
1018 {
1019     char mode;
1020
1021     daemon_assert(invariant(f));
1022     daemon_assert(cmd != NULL);
1023     daemon_assert(cmd->num_arg == 1);
1024
1025     mode = cmd->arg[0].string[0];
1026     if (mode == 'S') {
1027         reply(f, 200, "Command okay.");
1028     } else {
1029         reply(f, 504, "Command not implemented for that parameter.");
1030     }
1031
1032     daemon_assert(invariant(f));
1033 }
1034
1035 /* convert the user-entered file name into a full path on our local drive */
1036 static void get_absolute_fname(char *fname, 
1037                                int fname_len,
1038                                const char *dir,
1039                                const char *file)
1040 {
1041     daemon_assert(fname != NULL);
1042     daemon_assert(dir != NULL);
1043     daemon_assert(file != NULL);
1044
1045     if (*file == '/') {
1046
1047         /* absolute path, use as input */
1048         daemon_assert(strlen(file) < fname_len);
1049         strcpy(fname, file);
1050
1051     } else {
1052
1053         /* construct a file name based on our current directory */
1054         daemon_assert(strlen(dir) + 1 + strlen(file) < fname_len);
1055         strcpy(fname, dir);
1056
1057         /* add a seperating '/' if we're not at the root */
1058         if (fname[1] != '\0') {
1059             strcat(fname, "/");
1060         }
1061
1062         /* and of course the actual file name */
1063         strcat(fname, file);
1064
1065     }
1066 }
1067
1068 static void do_retr(ftp_session_t *f, const ftp_command_t *cmd) 
1069 {
1070     const char *file_name;
1071     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1072     int file_fd;
1073     struct stat stat_buf;
1074     int socket_fd;
1075     int read_ret;
1076     char buf[4096];
1077     char converted_buf[8192];
1078     int converted_buflen;
1079     char addr_port[ADDRPORT_STRLEN];
1080     struct timeval start_timestamp;
1081     struct timeval end_timestamp;
1082     struct timeval transfer_time;
1083     off_t file_size;
1084     off_t offset;
1085     off_t amt_to_send;
1086     int sendfile_ret;
1087     off_t amt_sent;
1088
1089     daemon_assert(invariant(f));
1090     daemon_assert(cmd != NULL);
1091     daemon_assert(cmd->num_arg == 1);
1092
1093     /* set up for exit */
1094     file_fd = -1;
1095     socket_fd = -1;
1096
1097     /* create an absolute name for our file */
1098     file_name = cmd->arg[0].string;
1099     get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1100
1101     /* open file */
1102     file_fd = open(full_path, O_RDONLY);
1103     reopen_syslog_hack (file_fd);
1104     if (file_fd == -1) {
1105         reply(f, 550, "Error opening file; %s.", strerror(errno));
1106         goto exit_retr;
1107     }
1108     if (fstat(file_fd, &stat_buf) != 0) {
1109         reply(f, 550, "Error getting file information; %s.", strerror(errno));
1110         goto exit_retr;
1111     }
1112 #ifndef STATS_MACRO_BROKEN
1113     if (S_ISDIR(stat_buf.st_mode)) {
1114 #else
1115     if (!S_ISDIR(stat_buf.st_mode)) {
1116 #endif
1117         reply(f, 550, "Error, file is a directory.");
1118         goto exit_retr;
1119     }
1120
1121     /* if the last command was a REST command, restart at the */
1122     /* requested position in the file                         */
1123     if ((f->file_offset_command_number == (f->command_number - 1)) && 
1124         (f->file_offset > 0))
1125     {
1126         if (lseek(file_fd, f->file_offset, SEEK_SET) == -1) {
1127             reply(f, 550, "Error seeking to restart position; %s.", 
1128                 strerror(errno));
1129             goto exit_retr;
1130         }
1131     }
1132
1133     /* ready to transfer */
1134     reply(f, 150, "About to open data connection.");
1135
1136     /* mark start time */
1137     gettimeofday(&start_timestamp, NULL);
1138
1139     /* open data path */
1140     socket_fd = open_connection(f);
1141     if (socket_fd == -1) {
1142         goto exit_retr;
1143     }
1144
1145     /* we're golden, send the file */
1146     file_size = 0;
1147     if (f->data_type == TYPE_ASCII) {
1148         for (;;) {
1149             read_ret = read(file_fd, buf, sizeof(buf));
1150             if (read_ret == -1) {
1151                 reply(f, 550, "Error reading from file; %s.", strerror(errno));
1152                 goto exit_retr;
1153             }
1154             if (read_ret == 0) {
1155                 break;
1156             }
1157             converted_buflen = convert_newlines(converted_buf, buf, read_ret);
1158
1159             if (write_fully(socket_fd, converted_buf, converted_buflen) == -1) {
1160                 reply(f, 550, "Error writing to data connection; %s.", 
1161                   strerror(errno));
1162                 goto exit_retr;
1163             }
1164
1165             file_size += converted_buflen;
1166         } 
1167     } else {
1168         daemon_assert(f->data_type == TYPE_IMAGE);
1169         
1170         /* for sendfile(), we still have to use a loop to avoid 
1171            having our watchdog time us out on large files - it does
1172            allow us to avoid an extra copy to/from user space */
1173 #ifdef HAVE_SENDFILE
1174         offset = f->file_offset;
1175         file_size = stat_buf.st_size - offset;
1176         while (offset < stat_buf.st_size) {
1177
1178             amt_to_send = stat_buf.st_size - offset;
1179             if (amt_to_send > 65536) {
1180                 amt_to_send = 65536;
1181             }
1182 #ifdef HAVE_LINUX_SENDFILE
1183             sendfile_ret = sendfile(socket_fd, 
1184                                     file_fd, 
1185                                     &offset, 
1186                                     amt_to_send);
1187             if (sendfile_ret != amt_to_send) {
1188                 reply(f, 550, "Error sending file; %s.", strerror(errno));
1189                 goto exit_retr;
1190             }
1191 #elif HAVE_FREEBSD_SENDFILE
1192             sendfile_ret = sendfile(file_fd, 
1193                                     socket_fd, 
1194                                     offset,
1195                                     amt_to_send,
1196                                     NULL,
1197                                     &amt_sent,
1198                                     0);
1199             if (sendfile_ret != 0) {
1200                 reply(f, 550, "Error sending file; %s.", strerror(errno));
1201                 goto exit_retr;
1202             }
1203             offset += amt_sent;
1204 #endif
1205
1206             watchdog_defer_watched(f->watched);
1207         }
1208 #else
1209         for (;;) {
1210             read_ret = read(file_fd, buf, sizeof(buf));
1211             if (read_ret == -1) {
1212                 reply(f, 550, "Error reading from file; %s.", strerror(errno));
1213                 goto exit_retr;
1214             }
1215             if (read_ret == 0) {
1216                 break;
1217             }
1218             if (write_fully(socket_fd, buf, read_ret) == -1) {
1219                 reply(f, 550, "Error writing to data connection; %s.", 
1220                   strerror(errno));
1221                 goto exit_retr;
1222             }
1223             file_size += read_ret;
1224
1225             watchdog_defer_watched(f->watched);
1226         }
1227 #endif  /* HAVE_SENDFILE */
1228     }
1229
1230     /* disconnect */
1231     close(socket_fd);
1232     socket_fd = -1;
1233
1234     /* hey, it worked, let the other side know */
1235     reply(f, 226, "File transfer complete.");
1236
1237     /* mark end time */
1238     gettimeofday(&end_timestamp, NULL);
1239
1240     /* calculate transfer rate */
1241     transfer_time.tv_sec = end_timestamp.tv_sec - start_timestamp.tv_sec;
1242     transfer_time.tv_usec = end_timestamp.tv_usec - start_timestamp.tv_usec;
1243     while (transfer_time.tv_usec >= 1000000) {
1244         transfer_time.tv_sec++;
1245         transfer_time.tv_usec -= 1000000;
1246     }
1247     while (transfer_time.tv_usec < 0) {
1248         transfer_time.tv_sec--;
1249         transfer_time.tv_usec += 1000000;
1250     }
1251
1252     /* note the transfer */
1253     syslog(LOG_INFO, 
1254       "%s retrieved \"%s\", %ld bytes in %d.%06d seconds", 
1255       f->client_addr_str, 
1256       full_path,
1257       file_size,
1258       transfer_time.tv_sec,
1259       transfer_time.tv_usec);
1260
1261 exit_retr:
1262     f->file_offset = 0;
1263     if (socket_fd != -1) {
1264         close(socket_fd);
1265     }
1266     if (file_fd != -1) {
1267         close(file_fd);
1268     }
1269     daemon_assert(invariant(f));
1270 }
1271
1272 static void do_stor(ftp_session_t *f, const ftp_command_t *cmd) 
1273 {
1274     daemon_assert(invariant(f));
1275     daemon_assert(cmd != NULL);
1276     daemon_assert(cmd->num_arg == 1);
1277
1278     reply(f, 553, "Server will not store files.");
1279
1280     daemon_assert(invariant(f));
1281 }
1282
1283 static int open_connection(ftp_session_t *f)
1284 {
1285     int socket_fd;
1286     struct sockaddr_in addr;
1287     unsigned addr_len;
1288
1289     daemon_assert((f->data_channel == DATA_PORT) || 
1290                   (f->data_channel == DATA_PASSIVE));
1291
1292     if (f->data_channel == DATA_PORT) {
1293         socket_fd = socket(SSFAM(&f->data_port), SOCK_STREAM, 0);
1294         reopen_syslog_hack (socket_fd);
1295         if (socket_fd == -1) {
1296             reply(f, 425, "Error creating socket; %s.", strerror(errno));
1297             return -1;
1298         }
1299         if (connect(socket_fd, (struct sockaddr *)&f->data_port, 
1300             sizeof(sockaddr_storage_t)) != 0)
1301         {
1302             reply(f, 425, "Error connecting; %s.", strerror(errno));
1303             close(socket_fd);
1304             return -1;
1305         }
1306     } else {
1307         daemon_assert(f->data_channel == DATA_PASSIVE);
1308
1309         addr_len = sizeof(struct sockaddr_in);
1310         socket_fd = accept(f->server_fd, (struct sockaddr *)&addr, &addr_len);
1311         reopen_syslog_hack (socket_fd);
1312         if (socket_fd == -1) {
1313             reply(f, 425, "Error accepting connection; %s.", strerror(errno));
1314             return -1;
1315         }
1316 #ifdef INET6
1317         /* in IPv6, the client can connect to a channel using a different */
1318         /* protocol - in that case, we'll just blindly let the connection */
1319         /* through, otherwise verify addresses match */
1320         if (SAFAM(addr) == SSFAM(&f->client_addr)) {
1321             if (memcmp(&SINADDR(&f->client_addr), &SINADDR(&addr), 
1322                        sizeof(SINADDR(&addr))))
1323             {
1324                 reply(f, 425, 
1325                   "Error accepting connection; connection from invalid IP.");
1326                 close(socket_fd);
1327                 return -1;
1328             }
1329         }
1330 #else
1331         if (memcmp(&f->client_addr.sin_addr, 
1332             &addr.sin_addr, sizeof(struct in_addr))) 
1333         {
1334             reply(f, 425, 
1335               "Error accepting connection; connection from invalid IP.");
1336             close(socket_fd);
1337             return -1;
1338         }
1339 #endif
1340     }
1341
1342     return socket_fd;
1343 }
1344
1345 /* convert any '\n' to '\r\n' */
1346 /* destination should be twice the size of the source for safety */
1347 static int convert_newlines(char *dst, const char *src, int srclen)
1348 {
1349     int i;
1350     int dstlen;
1351
1352     daemon_assert(dst != NULL);
1353     daemon_assert(src != NULL);
1354
1355     dstlen = 0;
1356     for (i=0; i<srclen; i++) {
1357         if (src[i] == '\n') {
1358             dst[dstlen++] = '\r';
1359         }
1360         dst[dstlen++] = src[i];
1361     }
1362     return dstlen;
1363 }
1364
1365 static int write_fully(int fd, const char *buf, int buflen)
1366 {
1367     int amt_written;
1368     int write_ret;
1369
1370     amt_written = 0;
1371     while (amt_written < buflen) {
1372         write_ret = write(fd, buf+amt_written, buflen-amt_written);
1373         if (write_ret <= 0) {
1374             return -1;
1375         }
1376         amt_written += write_ret;
1377     }
1378     return amt_written;
1379 }
1380
1381 static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd) 
1382 {
1383     daemon_assert(invariant(f));
1384     daemon_assert(cmd != NULL);
1385     daemon_assert(cmd->num_arg == 0);
1386
1387     reply(f, 257, "\"%s\" is current directory.", f->dir);
1388
1389     daemon_assert(invariant(f));
1390 }
1391
1392 #if 0
1393 /* 
1394   because oftpd uses glob(), it is possible for users to launch a 
1395   denial-of-service attack by sending certain wildcard expressions that
1396   create extremely large lists of files, e.g. "*/../*/../*/../*/../*/../*"
1397
1398   in order to prevent this, a user may pass wildcards, or paths, but not
1399   both as arguments to LIST or NLST - at most all the files from a single 
1400   directory will be returned
1401 */
1402 #endif
1403
1404 /* check if a filespec has a wildcard in it */
1405 static int filespec_has_wildcard(const char *filespec)
1406 {
1407     daemon_assert(filespec != NULL);
1408
1409     /* check each character for wildcard */
1410     while (*filespec != '\0') {
1411         switch (*filespec) {
1412             /* wildcards */
1413             case '*':
1414             case '?':
1415             case '[':
1416                 return 1;
1417
1418             /* backslash escapes next character unless at end of string */
1419             case '\\':
1420                 if (*(filespec+1) != '\0') {
1421                     filespec++;
1422                 }
1423                 break;
1424         }
1425         filespec++;
1426     }
1427
1428     return 0;
1429 }
1430
1431 /* filespec includes path separator, i.e. '/' */
1432 static int filespec_has_path_separator(const char *filespec)
1433 {
1434     daemon_assert(filespec != NULL);
1435
1436     /* check each character for path separator */
1437     if (strchr(filespec, '/') != NULL) {
1438         return 1;
1439     } else {
1440         return 0;
1441     }
1442 }
1443
1444 /* returns whether filespec is legal or not */
1445 static int filespec_is_legal(const char *filespec)
1446 {
1447     daemon_assert(filespec != NULL);
1448
1449     if (filespec_has_wildcard(filespec)) {
1450         if (filespec_has_path_separator(filespec)) {
1451             return 0;
1452         }
1453     }
1454     return 1;
1455 }
1456
1457 static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd) 
1458 {
1459     int fd;
1460     const char *param;
1461     int send_ok;
1462
1463     daemon_assert(invariant(f));
1464     daemon_assert(cmd != NULL);
1465     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
1466
1467     /* set up for exit */
1468     fd = -1;
1469
1470     /* figure out what parameters to use */
1471     if (cmd->num_arg == 0) {
1472         param = "*";
1473     } else {
1474         daemon_assert(cmd->num_arg == 1);
1475
1476         /* ignore attempts to send options to "ls" by silently dropping */
1477         if (cmd->arg[0].string[0] == '-') {
1478             param = "*";
1479         } else {
1480             param = cmd->arg[0].string;
1481         }
1482     }
1483
1484     /* check spec passed */
1485     if (!filespec_is_legal(param)) {
1486         reply(f, 550, "Illegal filename passed.");
1487         goto exit_nlst;
1488     }
1489
1490     /* ready to list */
1491     reply(f, 150, "About to send name list.");
1492
1493     /* open our data connection */
1494     fd = open_connection(f);
1495     if (fd == -1) {
1496         goto exit_nlst;
1497     }
1498
1499     /* send any files */
1500     send_ok = file_nlst(fd, f->dir, param);
1501
1502     /* strange handshake for Netscape's benefit */
1503     netscape_hack(fd);
1504
1505     if (send_ok) {
1506         reply(f, 226, "Transfer complete.");
1507     } else {
1508         reply(f, 451, "Error sending name list.");
1509     }
1510
1511     /* clean up and exit */
1512 exit_nlst:
1513     if (fd != -1) {
1514         close(fd);
1515     }
1516     daemon_assert(invariant(f));
1517 }
1518
1519 static void do_list(ftp_session_t *f, const ftp_command_t *cmd) 
1520 {
1521     int fd;
1522     const char *param;
1523     int send_ok;
1524
1525     daemon_assert(invariant(f));
1526     daemon_assert(cmd != NULL);
1527     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
1528
1529     /* set up for exit */
1530     fd = -1;
1531
1532     /* figure out what parameters to use */
1533     if (cmd->num_arg == 0) {
1534         param = "*";
1535     } else {
1536         daemon_assert(cmd->num_arg == 1);
1537
1538         /* ignore attempts to send options to "ls" by silently dropping */
1539         if (cmd->arg[0].string[0] == '-') {
1540             param = "*";
1541         } else {
1542             param = cmd->arg[0].string;
1543         }
1544     }
1545
1546     /* check spec passed */
1547     if (!filespec_is_legal(param)) {
1548         reply(f, 550, "Illegal filename passed.");
1549         goto exit_list;
1550     }
1551
1552     /* ready to list */
1553     reply(f, 150, "About to send file list.");
1554
1555     /* open our data connection */
1556     fd = open_connection(f);
1557     if (fd == -1) {
1558         goto exit_list;
1559     }
1560
1561     /* send any files */
1562     send_ok = file_list(fd, f->dir, param);
1563
1564     /* strange handshake for Netscape's benefit */
1565     netscape_hack(fd);
1566
1567     if (send_ok) {
1568         reply(f, 226, "Transfer complete.");
1569     } else {
1570         reply(f, 451, "Error sending file list.");
1571     }
1572
1573     /* clean up and exit */
1574 exit_list:
1575     if (fd != -1) {
1576         close(fd);
1577     }
1578     daemon_assert(invariant(f));
1579 }
1580
1581 static void do_syst(ftp_session_t *f, const ftp_command_t *cmd) 
1582 {
1583     daemon_assert(invariant(f));
1584     daemon_assert(cmd != NULL);
1585     daemon_assert(cmd->num_arg == 0);
1586
1587     reply(f, 215, "UNIX");
1588
1589     daemon_assert(invariant(f));
1590 }
1591
1592
1593 static void do_noop(ftp_session_t *f, const ftp_command_t *cmd) 
1594 {
1595     daemon_assert(invariant(f));
1596     daemon_assert(cmd != NULL);
1597     daemon_assert(cmd->num_arg == 0);
1598
1599     reply(f, 200, "Command okay.");
1600
1601     daemon_assert(invariant(f));
1602 }
1603
1604 static void do_rest(ftp_session_t *f, const ftp_command_t *cmd) 
1605 {
1606     daemon_assert(invariant(f));
1607     daemon_assert(cmd != NULL);
1608     daemon_assert(cmd->num_arg == 1);
1609
1610     if (f->data_type != TYPE_IMAGE) {
1611         reply(f, 555, "Restart not possible in ASCII mode.");
1612     } else if (f->file_structure != STRU_FILE) {
1613         reply(f, 555, "Restart only possible with FILE structure.");
1614     } else {
1615         f->file_offset = cmd->arg[0].offset;
1616         f->file_offset_command_number = f->command_number;
1617         reply(f, 350, "Restart okay, awaiting file retrieval request.");
1618     }
1619
1620     daemon_assert(invariant(f));
1621 }
1622
1623 static void do_size(ftp_session_t *f, const ftp_command_t *cmd) 
1624 {
1625     const char *file_name;
1626     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1627     struct stat stat_buf;
1628     
1629     daemon_assert(invariant(f));
1630     daemon_assert(cmd != NULL);
1631     daemon_assert(cmd->num_arg == 1);
1632
1633     if (f->data_type != TYPE_IMAGE) {
1634         reply(f, 550, "Size cannot be determined in ASCII mode.");
1635     } else if (f->file_structure != STRU_FILE) {
1636         reply(f, 550, "Size cannot be determined with FILE structure.");
1637     } else {
1638
1639         /* create an absolute name for our file */
1640         file_name = cmd->arg[0].string;
1641         get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1642
1643         /* get the file information */
1644         if (stat(full_path, &stat_buf) != 0) {
1645             reply(f, 550, "Error getting file status; %s.", strerror(errno));
1646         } else {
1647             /* output the size */
1648             if (sizeof(off_t) == 8) {
1649                 reply(f, 213, "%llu", stat_buf.st_size);
1650             } else {
1651                 reply(f, 213, "%lu", stat_buf.st_size);
1652             } 
1653         }
1654
1655     }
1656
1657     daemon_assert(invariant(f));
1658 }
1659
1660 /* if no gmtime_r() is available, provide one */
1661 #ifndef HAVE_GMTIME_R
1662 struct tm *gmtime_r(const time_t *timep, struct tm *timeptr) 
1663 {
1664     static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
1665
1666     pthread_mutex_lock(&time_lock);
1667     *timeptr = *(gmtime(timep));
1668     pthread_mutex_unlock(&time_lock);
1669     return timeptr;
1670 }
1671 #endif /* HAVE_GMTIME_R */
1672
1673 static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd) 
1674 {
1675     const char *file_name;
1676     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1677     struct stat stat_buf;
1678     struct tm mtime;
1679     char time_buf[16];
1680     
1681     daemon_assert(invariant(f));
1682     daemon_assert(cmd != NULL);
1683     daemon_assert(cmd->num_arg == 1);
1684
1685     /* create an absolute name for our file */
1686     file_name = cmd->arg[0].string;
1687     get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1688
1689     /* get the file information */
1690     if (stat(full_path, &stat_buf) != 0) {
1691         reply(f, 550, "Error getting file status; %s.", strerror(errno));
1692     } else {
1693         gmtime_r(&stat_buf.st_mtime, &mtime);
1694         strftime(time_buf, sizeof(time_buf), "%Y%m%d%H%M%S", &mtime);
1695         reply(f, 213, time_buf);
1696     }
1697
1698     daemon_assert(invariant(f));
1699 }
1700
1701
1702 static void send_readme(const ftp_session_t *f, int code)
1703 {
1704     char file_name[PATH_MAX+1];
1705     int dir_len;
1706     struct stat stat_buf;
1707     int fd;
1708     int read_ret;
1709     char buf[4096];
1710     char code_str[8];
1711     char *p;
1712     int len;
1713     char *nl;
1714     int line_len;
1715
1716     daemon_assert(invariant(f));
1717     daemon_assert(code >= 100);
1718     daemon_assert(code <= 559);
1719
1720     /* set up for early exit */
1721     fd = -1;
1722
1723     /* verify our README wouldn't be too long */
1724     dir_len = strlen(f->dir);
1725     if ((dir_len + 1 + sizeof(README_FILE_NAME)) > sizeof(file_name)) {
1726         goto exit_send_readme;
1727     }
1728
1729     /* create a README file name */
1730     strcpy(file_name, f->dir);
1731     strcat(file_name, "/");
1732     strcat(file_name, README_FILE_NAME);
1733
1734     /* open our file */
1735     fd = open(file_name, O_RDONLY);
1736     reopen_syslog_hack (fd);
1737     if (fd == -1) {
1738         goto exit_send_readme;
1739     }
1740
1741     /* verify this isn't a directory */
1742     if (fstat(fd, &stat_buf) != 0) {
1743         goto exit_send_readme;
1744     }
1745 #ifndef STATS_MACRO_BROKEN
1746     if (S_ISDIR(stat_buf.st_mode)) {
1747 #else
1748     if (!S_ISDIR(stat_buf.st_mode)) {
1749 #endif
1750         goto exit_send_readme;
1751     }
1752
1753     /* convert our code to a buffer */
1754     daemon_assert(code >= 100);
1755     daemon_assert(code <= 999);
1756     sprintf(code_str, "%03d-", code);
1757
1758     /* read and send */
1759     read_ret = read(fd, buf, sizeof(buf));
1760     if (read_ret > 0) {
1761         telnet_session_print(f->telnet_session, code_str);
1762         while (read_ret > 0) {
1763             p = buf;
1764             len = read_ret;
1765             nl = memchr(p, '\n', len);
1766             while ((len > 0) && (nl != NULL)) {
1767                 *nl = '\0';
1768                 telnet_session_println(f->telnet_session, p);
1769                 line_len = nl - p;
1770                 len -= line_len + 1;
1771                 if (len > 0) {
1772                     telnet_session_print(f->telnet_session, code_str);
1773                 }
1774                 p = nl+1;
1775                 nl = memchr(p, '\n', len);
1776             }
1777             if (len > 0) {
1778                 telnet_session_print(f->telnet_session, p);
1779             }
1780
1781             read_ret = read(fd, buf, sizeof(buf));
1782         }
1783     }
1784
1785     /* cleanup and exit */
1786 exit_send_readme:
1787     if (fd != -1) {
1788         close(fd);
1789     }
1790     daemon_assert(invariant(f));
1791 }
1792
1793 /* hack which prevents Netscape error in file list */
1794 static void netscape_hack(int fd)
1795 {
1796     fd_set readfds;
1797     struct timeval timeout;
1798     int select_ret;
1799     char c;
1800
1801     daemon_assert(fd >= 0);
1802
1803     shutdown(fd, 1);
1804     FD_ZERO(&readfds);
1805     FD_SET(fd, &readfds);
1806     timeout.tv_sec = 15;
1807     timeout.tv_usec = 0;
1808     select_ret = select(fd+1, &readfds, NULL, NULL, &timeout);
1809     if (select_ret > 0) {
1810         read(fd, &c, 1);
1811     }
1812 }
1813
1814 /* compare two addresses to see if they contain the same IP address */
1815 static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b)
1816 {
1817     daemon_assert(a != NULL);
1818     daemon_assert(b != NULL);
1819     daemon_assert((SSFAM(a) == AF_INET) || (SSFAM(a) == AF_INET6));
1820     daemon_assert((SSFAM(b) == AF_INET) || (SSFAM(b) == AF_INET6));
1821
1822     if (SSFAM(a) != SSFAM(b)) {
1823         return 0;
1824     }
1825     if (memcmp(&SINADDR(a), &SINADDR(b), sizeof(SINADDR(a))) != 0) {
1826         return 0;
1827     }
1828     return 1;
1829 }
1830