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