A couple of changes collected over the last year
[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             char errbuf[ERRBUF_SIZE];
154             error_init(err, 0, "unable to determing IPv4 address; %s",
155                 gai_strerror_r(errcode, errbuf, ERRBUF_SIZE));
156             return 0;
157         }
158
159         /* let's sanity check */
160         daemon_assert(res != NULL);
161         daemon_assert(sizeof(f->server_ipv4_addr) >= res->ai_addrlen);
162         daemon_assert(SSFAM(host_port) == AF_INET);
163
164         /* copy the result and free memory as necessary */
165         memcpy(&f->server_ipv4_addr, res->ai_addr, res->ai_addrlen);
166         freeaddrinfo(res);
167     } else {
168         daemon_assert(SSFAM(host_port) == AF_INET);
169         f->server_ipv4_addr = *server_addr;
170     }
171 #else
172     f->server_ipv4_addr = *server_addr;
173 #endif
174
175     f->session_active = 1;
176     f->command_number = 0;
177
178     f->data_type = TYPE_ASCII;
179     f->file_structure = STRU_FILE;
180
181     f->file_offset = 0;
182     f->file_offset_command_number = ULONG_MAX;
183
184     f->epsv_all_set = 0;
185
186     f->client_addr = *client_addr;
187     get_addr_str(client_addr, f->client_addr_str, sizeof(f->client_addr_str));
188
189     f->server_addr = *server_addr;
190
191     f->telnet_session = t;
192     daemon_assert(strlen(dir) < sizeof(f->dir));
193     strcpy(f->dir, dir);
194
195     f->data_channel = DATA_PORT;
196     f->data_port = *client_addr;
197     f->server_fd = -1;
198
199     daemon_assert(invariant(f));
200
201     return 1;
202 }
203
204 void ftp_session_drop(ftp_session_t *f, const char *reason)
205 {
206     daemon_assert(invariant(f));
207     daemon_assert(reason != NULL);
208
209     /* say goodbye */
210     reply(f, 421, "%s.", reason);
211
212     daemon_assert(invariant(f));
213 }
214
215 void ftp_session_run(ftp_session_t *f, watched_t *watched)
216 {
217     char buf[2048];
218     int len;
219     ftp_command_t cmd;
220     int i;
221
222     daemon_assert(invariant(f));
223     daemon_assert(watched != NULL);
224
225     /* record our watchdog */
226     f->watched = watched;
227
228     /* say hello */
229     send_readme(f, 220);
230     reply(f, 220, "Service ready for new user.");
231
232     /* process commands */
233     while (f->session_active && 
234         telnet_session_readln(f->telnet_session, buf, sizeof(buf))) 
235     {
236      
237         /* delay our timeout based on this input */
238         watchdog_defer_watched(f->watched);
239
240         /* increase our command count */
241         if (f->command_number == ULONG_MAX) {
242             f->command_number = 0;
243         } else {
244             f->command_number++;
245         }
246     
247         /* make sure we read a whole line */
248         len = strlen(buf);
249         if (buf[len-1] != '\n') {
250             reply(f, 500, "Command line too long.");
251             while (telnet_session_readln(f->telnet_session, buf, sizeof(buf))) {
252                 len = strlen(buf);
253                 if (buf[len-1] == '\n') {
254                     break;
255                 }
256             }
257             goto next_command;
258         }
259
260         syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
261
262         /* parse the line */
263         if (!ftp_command_parse(buf, &cmd)) {
264             reply(f, 500, "Syntax error, command unrecognized.");
265             goto next_command;
266         }
267
268         /* dispatch the command */
269         for (i=0; i<NUM_COMMAND_FUNC; i++) {
270             if (strcmp(cmd.command, command_func[i].name) == 0) {
271                 (command_func[i].func)(f, &cmd);
272                 goto next_command;
273             }
274         }
275
276         /* oops, we don't have this command (shouldn't happen - shrug) */
277         reply(f, 502, "Command not implemented.");
278
279 next_command: {}
280     }
281
282     daemon_assert(invariant(f));
283 }
284
285 void ftp_session_destroy(ftp_session_t *f) 
286 {
287     daemon_assert(invariant(f));
288
289     if (f->server_fd != -1) {
290         close(f->server_fd);
291         f->server_fd = -1;
292     }
293 }
294
295 #ifndef NDEBUG
296 static int invariant(const ftp_session_t *f)
297 {
298     int len;
299
300     if (f == NULL) {
301         return 0;
302     }
303     if ((f->session_active != 0) && (f->session_active != 1)) {
304         return 0;
305     }
306     if ((f->data_type != TYPE_ASCII) && (f->data_type != TYPE_IMAGE)) {
307         return 0;
308     }
309     if ((f->file_structure != STRU_FILE) && (f->file_structure != STRU_RECORD)){
310         return 0;
311     }
312     if (f->file_offset < 0) {
313         return 0;
314     }
315     if ((f->epsv_all_set != 0) && (f->epsv_all_set != 1)) {
316         return 0;
317     }
318     len = strlen(f->client_addr_str);
319     if ((len <= 0) || (len >= ADDRPORT_STRLEN)) {
320         return 0;
321     }
322     if (f->telnet_session == NULL) {
323         return 0;
324     }
325     switch (f->data_channel) {
326         case DATA_PORT:
327             /* If the client specifies a port, verify that it is from the   */
328             /* host the client connected from.  This prevents a client from */
329             /* using the server to open connections to arbritrary hosts.    */
330             if (!ip_equal(&f->client_addr, &f->data_port)) {
331                 return 0;
332             }
333             if (f->server_fd != -1) {
334                 return 0;
335             }
336             break;
337         case DATA_PASSIVE:
338             if (f->server_fd < 0) {
339                 return 0;
340             }
341             break;
342         default:
343             return 0;
344     }
345     return 1;
346 }
347 #endif /* NDEBUG */
348
349 static void reply(ftp_session_t *f, int code, const char *fmt, ...)
350 {
351     char buf[256];
352     va_list ap;
353
354     daemon_assert(invariant(f));
355     daemon_assert(code >= 100);
356     daemon_assert(code <= 559);
357     daemon_assert(fmt != NULL);
358
359     /* prepend our code to the buffer */
360     sprintf(buf, "%d", code);
361     buf[3] = ' ';
362
363     /* add the formatted output of the caller to the buffer */
364     va_start(ap, fmt);
365     vsnprintf(buf+4, sizeof(buf)-4, fmt, ap);
366     va_end(ap);
367
368     /* log our reply */
369     syslog(LOG_DEBUG, "%s %s", f->client_addr_str, buf);
370
371     /* send the output to the other side */
372     telnet_session_println(f->telnet_session, buf);
373
374     daemon_assert(invariant(f));
375 }
376
377 static void do_user(ftp_session_t *f, const ftp_command_t *cmd) 
378 {
379     const char *user;
380     char addr_port[ADDRPORT_STRLEN];
381
382     daemon_assert(invariant(f));
383     daemon_assert(cmd != NULL);
384     daemon_assert(cmd->num_arg == 1);
385
386     user = cmd->arg[0].string;
387     if (strcasecmp(user, "ftp") && strcasecmp(user, "anonymous")) {
388         syslog(LOG_WARNING, "%s attempted to log in as \"%s\"", 
389             f->client_addr_str, user);
390         reply(f, 530, "Only anonymous FTP supported.");
391     } else {
392         reply(f, 331, "Send e-mail address as password.");
393     }
394     daemon_assert(invariant(f));
395 }
396
397
398 static void do_pass(ftp_session_t *f, const ftp_command_t *cmd) 
399 {
400     const char *password;
401     char addr_port[ADDRPORT_STRLEN];
402
403     daemon_assert(invariant(f));
404     daemon_assert(cmd != NULL);
405     daemon_assert(cmd->num_arg == 1);
406
407     password = cmd->arg[0].string;
408     syslog(LOG_INFO, "%s reports e-mail address \"%s\"", 
409         f->client_addr_str, password);
410     reply(f, 230, "User logged in, proceed.");
411
412     daemon_assert(invariant(f));
413 }
414
415 #ifdef INET6
416 static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
417 {
418     int port;
419     int error;
420     int len;
421
422     daemon_assert(s != NULL);
423     daemon_assert(buf != NULL);
424
425     /* buf must be able to contain (at least) a string representing an
426      * ipv4 addr, followed by the string " port " (6 chars) and the port
427      * number (which is 5 chars max), plus the '\0' character. */ 
428     daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
429
430     error = getnameinfo(client_addr, sizeof(sockaddr_storage_t), buf, 
431                 bufsiz, NULL, 0, NI_NUMERICHOST);
432     /* getnameinfo() should never fail when called with NI_NUMERICHOST */
433     daemon_assert(error == 0);
434
435     len = strlen(buf);
436     daemon_assert(bufsiz >= len+12);
437     snprintf(buf+len, bufsiz-len, " port %d", ntohs(SINPORT(&f->client_addr)));
438 }
439 #else
440 static void get_addr_str(const sockaddr_storage_t *s, char *buf, int bufsiz)
441 {
442     unsigned int addr;
443     int port;
444
445     daemon_assert(s != NULL);
446     daemon_assert(buf != NULL);
447
448     /* buf must be able to contain (at least) a string representing an
449      * ipv4 addr, followed by the string " port " (6 chars) and the port
450      * number (which is 5 chars max), plus the '\0' character. */ 
451     daemon_assert(bufsiz >= (INET_ADDRSTRLEN + 12));
452
453     addr = ntohl(s->sin_addr.s_addr);
454     port = ntohs(s->sin_port);
455     snprintf(buf, bufsiz, "%d.%d.%d.%d port %d", 
456         (addr >> 24) & 0xff, 
457         (addr >> 16) & 0xff,
458         (addr >> 8)  & 0xff,
459         addr & 0xff,
460         port);
461 }
462 #endif
463
464 static void do_cwd(ftp_session_t *f, const ftp_command_t *cmd) 
465 {
466     const char *new_dir;
467
468     daemon_assert(invariant(f));
469     daemon_assert(cmd != NULL);
470     daemon_assert(cmd->num_arg == 1);
471
472     new_dir = cmd->arg[0].string;
473     change_dir(f, new_dir);
474
475     daemon_assert(invariant(f));
476 }
477
478 static void do_cdup(ftp_session_t *f, const ftp_command_t *cmd) 
479 {
480     daemon_assert(invariant(f));
481     daemon_assert(cmd != NULL);
482     daemon_assert(cmd->num_arg == 0);
483
484     change_dir(f, "..");
485
486     daemon_assert(invariant(f));
487 }
488
489 static void change_dir(ftp_session_t *f, const char *new_dir)
490 {
491     char target[PATH_MAX+1];
492     const char *p, *n;
493     int len;
494     char *prev_dir;
495     char *target_end;
496
497     struct stat stat_buf;
498     int dir_okay;
499
500     daemon_assert(invariant(f));
501     daemon_assert(new_dir != NULL);
502     daemon_assert(strlen(new_dir) <= PATH_MAX);
503
504     /* set up our "base" directory that we build from */
505     p = new_dir;
506     if (*p == '/') {
507         /* if this starts with a '/' it is an absolute path */
508         strcpy(target, "/");
509         do {
510             p++;
511         } while (*p == '/');
512     } else {
513         /* otherwise it's a relative path */
514         daemon_assert(strlen(f->dir) < sizeof(target));
515         strcpy(target, f->dir);
516     }
517
518     /* add on each directory, handling "." and ".." */
519     while (*p != '\0') {
520
521         /* find the end of the next directory (either at '/' or '\0') */
522         n = strchr(p, '/');
523         if (n == NULL) {
524             n = strchr(p, '\0');
525         }
526         len = n - p;
527
528         if ((len == 1) && (p[0] == '.')) {
529
530             /* do nothing with "." */
531
532         } else if ((len == 2) && (p[0] == '.') && (p[1] == '.')) {
533
534             /* change to previous directory with ".." */
535             prev_dir = strrchr(target, '/');
536             daemon_assert(prev_dir != NULL);
537             *prev_dir = '\0';
538             if (prev_dir == target) {
539                 strcpy(target, "/");
540             }
541
542         } else {
543
544             /* otherwise add to current directory */
545             if ((strlen(target) + 1 + len) > PATH_MAX) {
546                 reply(f, 550, "Error changing directory; path is too long.");
547                 return;
548             }
549
550             /* append a '/' unless we were at the root directory */
551             target_end = strchr(target, '\0');
552             if (target_end != target+1) {
553                 *target_end++ = '/';
554             }
555
556             /* add the directory itself */
557             while (p != n) {
558                 *target_end++ = *p++;
559             }
560             *target_end = '\0';
561
562         }
563
564         /* advance to next directory to check */
565         p = n;
566
567         /* skip '/' characters */
568         while (*p == '/') {
569             p++;
570         }
571     }
572
573     /* see if this is a directory we can change into.  We don't allow
574        /dev as usual. */
575     dir_okay = 0;
576     if (!(!strncmp (target, "/dev", 4) && (!target[4] || target[4] == '/'))
577         && stat(target, &stat_buf) == 0) {
578 #ifndef STAT_MACROS_BROKEN
579         if (!S_ISDIR(stat_buf.st_mode)) {
580 #else
581         if (S_ISDIR(stat_buf.st_mode)) {
582 #endif
583             reply(f, 550,"Directory change failed; target is not a directory.");
584         } else { 
585             if (S_IXOTH & stat_buf.st_mode) {
586                 dir_okay = 1;
587             } else if ((stat_buf.st_gid == getegid()) && 
588                 (S_IXGRP & stat_buf.st_mode)) 
589             {
590                 dir_okay = 1;
591             } else if ((stat_buf.st_uid == geteuid()) && 
592                 (S_IXUSR & stat_buf.st_mode)) 
593             {
594                 dir_okay = 1;
595             } else {
596                 reply(f, 550, "Directory change failed; permission denied.");
597             }
598         }
599     } else {
600         reply(f, 550, "Directory change failed; directory does not exist.");
601     }
602
603     /* if everything is okay, change into the directory */
604     if (dir_okay) {
605         daemon_assert(strlen(target) < sizeof(f->dir));
606         /* send a readme unless we changed to our current directory */
607         if (strcmp(f->dir, target) != 0) {
608             strcpy(f->dir, target);
609             send_readme(f, 250);
610         } else {
611             strcpy(f->dir, target);
612         }
613         reply(f, 250, "Directory change successful.");
614     }
615
616     daemon_assert(invariant(f));
617 }
618
619 static void do_quit(ftp_session_t *f, const ftp_command_t *cmd) 
620 {
621     daemon_assert(invariant(f));
622     daemon_assert(cmd != NULL);
623     daemon_assert(cmd->num_arg == 0);
624
625     reply(f, 221, "Service closing control connection.");
626     f->session_active = 0;
627
628     daemon_assert(invariant(f));
629 }
630
631 /* support for the various port setting functions */
632 static void set_port(ftp_session_t *f, const sockaddr_storage_t *host_port)
633 {
634     daemon_assert(invariant(f));
635     daemon_assert(host_port != NULL);
636
637     if (f->epsv_all_set) {
638         reply(f, 500, "After EPSV ALL, only EPSV allowed.");
639     } else if (!ip_equal(&f->client_addr, host_port)) {
640         reply(f, 500, "Port must be on command channel IP.");
641     } else if (ntohs(SINPORT(host_port)) < IPPORT_RESERVED) {
642         reply(f, 500, "Port may not be less than 1024, which is reserved.");
643     } else {
644         /* close any outstanding PASSIVE port */
645         if (f->data_channel == DATA_PASSIVE) {
646             close(f->server_fd);
647             f->server_fd = -1;
648         }
649
650         f->data_channel = DATA_PORT;
651         f->data_port = *host_port;
652         reply(f, 200, "Command okay.");
653     }
654
655     daemon_assert(invariant(f));
656 }
657
658 /* set IP and port for client to receive data on */
659 static void do_port(ftp_session_t *f, const ftp_command_t *cmd) 
660 {
661     const sockaddr_storage_t *host_port;
662
663     daemon_assert(invariant(f));
664     daemon_assert(cmd != NULL);
665     daemon_assert(cmd->num_arg == 1);
666
667     host_port = &cmd->arg[0].host_port;
668     daemon_assert(SSFAM(host_port) == AF_INET);
669
670     set_port(f, host_port);
671
672     daemon_assert(invariant(f));
673 }
674
675 /* set IP and port for client to receive data on, transport independent */
676 static void do_lprt(ftp_session_t *f, const ftp_command_t *cmd)  
677 {
678     const sockaddr_storage_t *host_port;
679
680     daemon_assert(invariant(f));
681     daemon_assert(cmd != NULL);
682     daemon_assert(cmd->num_arg == 1);                                   
683
684     host_port = &cmd->arg[0].host_port;
685
686 #ifdef INET6
687     if ((SSFAM(host_port) != AF_INET) && (SSFAM(host_port) != AF_INET6)) {
688         reply(f, 521, "Only IPv4 and IPv6 supported, address families (4,6)");
689     }
690 #else
691     if (SSFAM(host_port) != AF_INET) {
692         reply(f, 521, "Only IPv4 supported, address family (4)");
693     }
694 #endif
695     else
696        set_port(f, host_port);
697
698     daemon_assert(invariant(f));
699 }
700
701 /* set IP and port for the client to receive data on, IPv6 extension */
702 /*                                                                   */
703 /* RFC 2428 specifies that if the data connection is going to be on  */
704 /* the same IP as the control connection, EPSV must be used.  Since  */
705 /* that is the only mode of transfer we support, we reject all EPRT  */
706 /* requests.                                                         */
707 static void do_eprt(ftp_session_t *f, const ftp_command_t *cmd)  
708 {
709     const sockaddr_storage_t *host_port;
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 "host_port" 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     char addr_port[ADDRPORT_STRLEN];
1088     struct timeval start_timestamp;
1089     struct timeval end_timestamp;
1090     struct timeval transfer_time;
1091     off_t file_size;
1092     off_t offset;
1093     off_t amt_to_send;
1094     int sendfile_ret;
1095     off_t amt_sent;
1096     char errbuf[ERRBUF_SIZE];
1097
1098     daemon_assert(invariant(f));
1099     daemon_assert(cmd != NULL);
1100     daemon_assert(cmd->num_arg == 1);
1101
1102     /* set up for exit */
1103     file_fd = -1;
1104     socket_fd = -1;
1105
1106     /* create an absolute name for our file */
1107     file_name = cmd->arg[0].string;
1108     get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1109     /* open file */
1110     if (!strncmp (full_path, "/dev", 4)
1111         && (!full_path[4] || full_path[4] == '/')) {
1112       file_fd = -1;
1113       errno = ENOENT;
1114     }
1115     else
1116       file_fd = open(full_path, O_RDONLY);
1117     if (file_fd == -1) {
1118         reply(f, 550, "Error opening file; %s.",
1119               strerror_r(errno, errbuf, ERRBUF_SIZE));
1120         goto exit_retr;
1121     }
1122     if (fstat(file_fd, &stat_buf) != 0) {
1123         reply(f, 550, "Error getting file information; %s.",
1124               strerror_r(errno, errbuf, ERRBUF_SIZE));
1125         goto exit_retr;
1126     }
1127 #ifndef STATS_MACRO_BROKEN
1128     if (S_ISDIR(stat_buf.st_mode)) {
1129 #else
1130     if (!S_ISDIR(stat_buf.st_mode)) {
1131 #endif
1132         reply(f, 550, "Error, file is a directory.");
1133         goto exit_retr;
1134     }
1135
1136     /* if the last command was a REST command, restart at the */
1137     /* requested position in the file                         */
1138     if ((f->file_offset_command_number == (f->command_number - 1)) && 
1139         (f->file_offset > 0))
1140     {
1141         if (lseek(file_fd, f->file_offset, SEEK_SET) == -1) {
1142             reply(f, 550, "Error seeking to restart position; %s.", 
1143                 strerror_r(errno, errbuf, ERRBUF_SIZE));
1144             goto exit_retr;
1145         }
1146     }
1147
1148     /* ready to transfer */
1149     reply(f, 150, "About to open data connection.");
1150
1151     /* mark start time */
1152     gettimeofday(&start_timestamp, NULL);
1153
1154     /* open data path */
1155     socket_fd = open_connection(f);
1156     if (socket_fd == -1) {
1157         goto exit_retr;
1158     }
1159
1160     /* we're golden, send the file */
1161     file_size = 0;
1162     if (f->data_type == TYPE_ASCII) {
1163         for (;;) {
1164             read_ret = read(file_fd, buf, sizeof(buf));
1165             if (read_ret == -1) {
1166                 reply(f, 550, "Error reading from file; %s.",
1167                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1168                 goto exit_retr;
1169             }
1170             if (read_ret == 0) {
1171                 break;
1172             }
1173             converted_buflen = convert_newlines(converted_buf, buf, read_ret);
1174
1175             if (write_fully(socket_fd, converted_buf, converted_buflen) == -1) {
1176                 reply(f, 550, "Error writing to data connection; %s.", 
1177                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1178                 goto exit_retr;
1179             }
1180
1181             file_size += converted_buflen;
1182         } 
1183     } else {
1184         daemon_assert(f->data_type == TYPE_IMAGE);
1185         
1186         /* for sendfile(), we still have to use a loop to avoid 
1187            having our watchdog time us out on large files - it does
1188            allow us to avoid an extra copy to/from user space */
1189 #ifdef HAVE_SENDFILE
1190         offset = f->file_offset;
1191         file_size = stat_buf.st_size - offset;
1192         while (offset < stat_buf.st_size) {
1193
1194             amt_to_send = stat_buf.st_size - offset;
1195             if (amt_to_send > 65536) {
1196                 amt_to_send = 65536;
1197             }
1198 #ifdef HAVE_LINUX_SENDFILE
1199             sendfile_ret = sendfile(socket_fd, 
1200                                     file_fd, 
1201                                     &offset, 
1202                                     amt_to_send);
1203             if (sendfile_ret != amt_to_send) {
1204                 reply(f, 550, "Error sending file; %s.",
1205                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1206                 goto exit_retr;
1207             }
1208 #elif HAVE_FREEBSD_SENDFILE
1209             sendfile_ret = sendfile(file_fd, 
1210                                     socket_fd, 
1211                                     offset,
1212                                     amt_to_send,
1213                                     NULL,
1214                                     &amt_sent,
1215                                     0);
1216             if (sendfile_ret != 0) {
1217                 reply(f, 550, "Error sending file; %s.",
1218                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1219                 goto exit_retr;
1220             }
1221             offset += amt_sent;
1222 #endif
1223
1224             watchdog_defer_watched(f->watched);
1225         }
1226 #else
1227         for (;;) {
1228             read_ret = read(file_fd, buf, sizeof(buf));
1229             if (read_ret == -1) {
1230                 reply(f, 550, "Error reading from file; %s.",
1231                       strerror_r(errno, errbuf, ERRBUF_SIZE));
1232                 goto exit_retr;
1233             }
1234             if (read_ret == 0) {
1235                 break;
1236             }
1237             if (write_fully(socket_fd, buf, read_ret) == -1) {
1238                 reply(f, 550, "Error writing to data connection; %s.", 
1239                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1240                 goto exit_retr;
1241             }
1242             file_size += read_ret;
1243
1244             watchdog_defer_watched(f->watched);
1245         }
1246 #endif  /* HAVE_SENDFILE */
1247     }
1248
1249     /* disconnect */
1250     close(socket_fd);
1251     socket_fd = -1;
1252
1253     /* hey, it worked, let the other side know */
1254     reply(f, 226, "File transfer complete.");
1255
1256     /* mark end time */
1257     gettimeofday(&end_timestamp, NULL);
1258
1259     /* calculate transfer rate */
1260     transfer_time.tv_sec = end_timestamp.tv_sec - start_timestamp.tv_sec;
1261     transfer_time.tv_usec = end_timestamp.tv_usec - start_timestamp.tv_usec;
1262     while (transfer_time.tv_usec >= 1000000) {
1263         transfer_time.tv_sec++;
1264         transfer_time.tv_usec -= 1000000;
1265     }
1266     while (transfer_time.tv_usec < 0) {
1267         transfer_time.tv_sec--;
1268         transfer_time.tv_usec += 1000000;
1269     }
1270
1271     /* note the transfer */
1272     syslog(LOG_INFO, 
1273       "%s retrieved \"%s\", %ld bytes in %d.%06d seconds", 
1274       f->client_addr_str, 
1275       full_path,
1276       file_size,
1277       transfer_time.tv_sec,
1278       transfer_time.tv_usec);
1279
1280 exit_retr:
1281     f->file_offset = 0;
1282     if (socket_fd != -1) {
1283         close(socket_fd);
1284     }
1285     if (file_fd != -1) {
1286         close(file_fd);
1287     }
1288     daemon_assert(invariant(f));
1289 }
1290
1291 static void do_stor(ftp_session_t *f, const ftp_command_t *cmd) 
1292 {
1293     daemon_assert(invariant(f));
1294     daemon_assert(cmd != NULL);
1295     daemon_assert(cmd->num_arg == 1);
1296
1297     reply(f, 553, "Server will not store files.");
1298
1299     daemon_assert(invariant(f));
1300 }
1301
1302 static int open_connection(ftp_session_t *f)
1303 {
1304     int socket_fd;
1305     struct sockaddr_in addr;
1306     unsigned addr_len;
1307     char errbuf[ERRBUF_SIZE];
1308
1309     daemon_assert((f->data_channel == DATA_PORT) || 
1310                   (f->data_channel == DATA_PASSIVE));
1311
1312     if (f->data_channel == DATA_PORT) {
1313         socket_fd = socket(SSFAM(&f->data_port), SOCK_STREAM, 0);
1314         if (socket_fd == -1) {
1315             reply(f, 425, "Error creating socket; %s.",
1316                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1317             return -1;
1318         }
1319         if (connect(socket_fd, (struct sockaddr *)&f->data_port, 
1320             sizeof(sockaddr_storage_t)) != 0)
1321         {
1322             reply(f, 425, "Error connecting; %s.",
1323                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1324             close(socket_fd);
1325             return -1;
1326         }
1327     } else {
1328         daemon_assert(f->data_channel == DATA_PASSIVE);
1329
1330         addr_len = sizeof(struct sockaddr_in);
1331         socket_fd = accept(f->server_fd, (struct sockaddr *)&addr, &addr_len);
1332         if (socket_fd == -1) {
1333             reply(f, 425, "Error accepting connection; %s.",
1334                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1335             return -1;
1336         }
1337 #ifdef INET6
1338         /* in IPv6, the client can connect to a channel using a different */
1339         /* protocol - in that case, we'll just blindly let the connection */
1340         /* through, otherwise verify addresses match */
1341         if (SAFAM(addr) == SSFAM(&f->client_addr)) {
1342             if (memcmp(&SINADDR(&f->client_addr), &SINADDR(&addr), 
1343                        sizeof(SINADDR(&addr))))
1344             {
1345                 reply(f, 425, 
1346                   "Error accepting connection; connection from invalid IP.");
1347                 close(socket_fd);
1348                 return -1;
1349             }
1350         }
1351 #else
1352         if (memcmp(&f->client_addr.sin_addr, 
1353             &addr.sin_addr, sizeof(struct in_addr))) 
1354         {
1355             reply(f, 425, 
1356               "Error accepting connection; connection from invalid IP.");
1357             close(socket_fd);
1358             return -1;
1359         }
1360 #endif
1361     }
1362
1363     return socket_fd;
1364 }
1365
1366 /* convert any '\n' to '\r\n' */
1367 /* destination should be twice the size of the source for safety */
1368 static int convert_newlines(char *dst, const char *src, int srclen)
1369 {
1370     int i;
1371     int dstlen;
1372
1373     daemon_assert(dst != NULL);
1374     daemon_assert(src != NULL);
1375
1376     dstlen = 0;
1377     for (i=0; i<srclen; i++) {
1378         if (src[i] == '\n') {
1379             dst[dstlen++] = '\r';
1380         }
1381         dst[dstlen++] = src[i];
1382     }
1383     return dstlen;
1384 }
1385
1386 static int write_fully(int fd, const char *buf, int buflen)
1387 {
1388     int amt_written;
1389     int write_ret;
1390
1391     amt_written = 0;
1392     while (amt_written < buflen) {
1393         write_ret = write(fd, buf+amt_written, buflen-amt_written);
1394         if (write_ret <= 0) {
1395             return -1;
1396         }
1397         amt_written += write_ret;
1398     }
1399     return amt_written;
1400 }
1401
1402 static void do_pwd(ftp_session_t *f, const ftp_command_t *cmd) 
1403 {
1404     daemon_assert(invariant(f));
1405     daemon_assert(cmd != NULL);
1406     daemon_assert(cmd->num_arg == 0);
1407
1408     reply(f, 257, "\"%s\" is current directory.", f->dir);
1409
1410     daemon_assert(invariant(f));
1411 }
1412
1413 #if 0
1414 /* 
1415   because oftpd uses glob(), it is possible for users to launch a 
1416   denial-of-service attack by sending certain wildcard expressions that
1417   create extremely large lists of files, e.g. "*/../*/../*/../*/../*/../*"
1418
1419   in order to prevent this, a user may pass wildcards, or paths, but not
1420   both as arguments to LIST or NLST - at most all the files from a single 
1421   directory will be returned
1422 */
1423 #endif
1424
1425 /* check if a filespec has a wildcard in it */
1426 static int filespec_has_wildcard(const char *filespec)
1427 {
1428     daemon_assert(filespec != NULL);
1429
1430     /* check each character for wildcard */
1431     while (*filespec != '\0') {
1432         switch (*filespec) {
1433             /* wildcards */
1434             case '*':
1435             case '?':
1436             case '[':
1437                 return 1;
1438
1439             /* backslash escapes next character unless at end of string */
1440             case '\\':
1441                 if (*(filespec+1) != '\0') {
1442                     filespec++;
1443                 }
1444                 break;
1445         }
1446         filespec++;
1447     }
1448
1449     return 0;
1450 }
1451
1452 /* filespec includes path separator, i.e. '/' */
1453 static int filespec_has_path_separator(const char *filespec)
1454 {
1455     daemon_assert(filespec != NULL);
1456
1457     /* check each character for path separator */
1458     if (strchr(filespec, '/') != NULL) {
1459         return 1;
1460     } else {
1461         return 0;
1462     }
1463 }
1464
1465 /* returns whether filespec is legal or not */
1466 static int filespec_is_legal(const char *filespec)
1467 {
1468     daemon_assert(filespec != NULL);
1469
1470     if (filespec_has_wildcard(filespec)) {
1471         if (filespec_has_path_separator(filespec)) {
1472             return 0;
1473         }
1474     }
1475     return 1;
1476 }
1477
1478 static void do_nlst(ftp_session_t *f, const ftp_command_t *cmd) 
1479 {
1480     int fd;
1481     const char *param;
1482     int send_ok;
1483
1484     daemon_assert(invariant(f));
1485     daemon_assert(cmd != NULL);
1486     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
1487
1488     /* set up for exit */
1489     fd = -1;
1490
1491     /* figure out what parameters to use */
1492     if (cmd->num_arg == 0) {
1493         param = "*";
1494     } else {
1495         daemon_assert(cmd->num_arg == 1);
1496
1497         /* ignore attempts to send options to "ls" by silently dropping */
1498         if (cmd->arg[0].string[0] == '-') {
1499             param = "*";
1500         } else {
1501             param = cmd->arg[0].string;
1502         }
1503     }
1504
1505     /* check spec passed */
1506     if (!filespec_is_legal(param)) {
1507         reply(f, 550, "Illegal filename passed.");
1508         goto exit_nlst;
1509     }
1510
1511     /* ready to list */
1512     reply(f, 150, "About to send name list.");
1513
1514     /* open our data connection */
1515     fd = open_connection(f);
1516     if (fd == -1) {
1517         goto exit_nlst;
1518     }
1519
1520     /* send any files */
1521     send_ok = file_nlst(fd, f->dir, param);
1522
1523     /* strange handshake for Netscape's benefit */
1524     netscape_hack(fd);
1525
1526     if (send_ok) {
1527         reply(f, 226, "Transfer complete.");
1528     } else {
1529         reply(f, 451, "Error sending name list.");
1530     }
1531
1532     /* clean up and exit */
1533 exit_nlst:
1534     if (fd != -1) {
1535         close(fd);
1536     }
1537     daemon_assert(invariant(f));
1538 }
1539
1540 static void do_list(ftp_session_t *f, const ftp_command_t *cmd) 
1541 {
1542     int fd;
1543     const char *param;
1544     int send_ok;
1545
1546     daemon_assert(invariant(f));
1547     daemon_assert(cmd != NULL);
1548     daemon_assert((cmd->num_arg == 0) || (cmd->num_arg == 1));
1549
1550     /* set up for exit */
1551     fd = -1;
1552
1553     /* figure out what parameters to use */
1554     if (cmd->num_arg == 0) {
1555         param = "*";
1556     } else {
1557         daemon_assert(cmd->num_arg == 1);
1558
1559         /* ignore attempts to send options to "ls" by silently dropping */
1560         if (cmd->arg[0].string[0] == '-') {
1561             param = "*";
1562         } else {
1563             param = cmd->arg[0].string;
1564         }
1565     }
1566
1567     /* check spec passed */
1568     if (!filespec_is_legal(param)) {
1569         reply(f, 550, "Illegal filename passed.");
1570         goto exit_list;
1571     }
1572
1573     /* ready to list */
1574     reply(f, 150, "About to send file list.");
1575
1576     /* open our data connection */
1577     fd = open_connection(f);
1578     if (fd == -1) {
1579         goto exit_list;
1580     }
1581
1582     /* send any files */
1583     send_ok = file_list(fd, f->dir, param);
1584
1585     /* strange handshake for Netscape's benefit */
1586     netscape_hack(fd);
1587
1588     if (send_ok) {
1589         reply(f, 226, "Transfer complete.");
1590     } else {
1591         reply(f, 451, "Error sending file list.");
1592     }
1593
1594     /* clean up and exit */
1595 exit_list:
1596     if (fd != -1) {
1597         close(fd);
1598     }
1599     daemon_assert(invariant(f));
1600 }
1601
1602 static void do_syst(ftp_session_t *f, const ftp_command_t *cmd) 
1603 {
1604     daemon_assert(invariant(f));
1605     daemon_assert(cmd != NULL);
1606     daemon_assert(cmd->num_arg == 0);
1607
1608     reply(f, 215, "UNIX");
1609
1610     daemon_assert(invariant(f));
1611 }
1612
1613
1614 static void do_noop(ftp_session_t *f, const ftp_command_t *cmd) 
1615 {
1616     daemon_assert(invariant(f));
1617     daemon_assert(cmd != NULL);
1618     daemon_assert(cmd->num_arg == 0);
1619
1620     reply(f, 200, "Command okay.");
1621
1622     daemon_assert(invariant(f));
1623 }
1624
1625 static void do_rest(ftp_session_t *f, const ftp_command_t *cmd) 
1626 {
1627     daemon_assert(invariant(f));
1628     daemon_assert(cmd != NULL);
1629     daemon_assert(cmd->num_arg == 1);
1630
1631     if (f->data_type != TYPE_IMAGE) {
1632         reply(f, 555, "Restart not possible in ASCII mode.");
1633     } else if (f->file_structure != STRU_FILE) {
1634         reply(f, 555, "Restart only possible with FILE structure.");
1635     } else {
1636         f->file_offset = cmd->arg[0].offset;
1637         f->file_offset_command_number = f->command_number;
1638         reply(f, 350, "Restart okay, awaiting file retrieval request.");
1639     }
1640
1641     daemon_assert(invariant(f));
1642 }
1643
1644 static void do_size(ftp_session_t *f, const ftp_command_t *cmd) 
1645 {
1646     const char *file_name;
1647     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1648     struct stat stat_buf;
1649     char errbuf[ERRBUF_SIZE];
1650     
1651     daemon_assert(invariant(f));
1652     daemon_assert(cmd != NULL);
1653     daemon_assert(cmd->num_arg == 1);
1654
1655     if (f->data_type != TYPE_IMAGE) {
1656         reply(f, 550, "Size cannot be determined in ASCII mode.");
1657     } else if (f->file_structure != STRU_FILE) {
1658         reply(f, 550, "Size cannot be determined with FILE structure.");
1659     } else {
1660
1661         /* create an absolute name for our file */
1662         file_name = cmd->arg[0].string;
1663         get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1664         /* get the file information */
1665         if (!strncmp (full_path, "/dev", 4)
1666             && (!full_path[4] || full_path[4] == '/')) {
1667             errno = ENOENT;
1668             reply(f, 550, "Error getting file status; %s.",
1669                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1670         }
1671         else if (stat(full_path, &stat_buf) != 0) {
1672             reply(f, 550, "Error getting file status; %s.",
1673                   strerror_r(errno, errbuf, ERRBUF_SIZE));
1674         }
1675         else if (S_ISDIR(stat_buf.st_mode)) {
1676             reply(f, 550, "File is a directory, SIZE command not valid.");
1677         } else {
1678             /* output the size */
1679             if (sizeof(off_t) == 8) {
1680                 reply(f, 213, "%llu", stat_buf.st_size);
1681             } else {
1682                 reply(f, 213, "%lu", stat_buf.st_size);
1683             } 
1684         }
1685
1686     }
1687
1688     daemon_assert(invariant(f));
1689 }
1690
1691 /* if no gmtime_r() is available, provide one */
1692 #ifndef HAVE_GMTIME_R
1693 struct tm *gmtime_r(const time_t *timep, struct tm *timeptr) 
1694 {
1695     static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
1696
1697     pthread_mutex_lock(&time_lock);
1698     *timeptr = *(gmtime(timep));
1699     pthread_mutex_unlock(&time_lock);
1700     return timeptr;
1701 }
1702 #endif /* HAVE_GMTIME_R */
1703
1704 static void do_mdtm(ftp_session_t *f, const ftp_command_t *cmd) 
1705 {
1706     const char *file_name;
1707     char full_path[PATH_MAX+1+MAX_STRING_LEN];
1708     struct stat stat_buf;
1709     struct tm mtime;
1710     char time_buf[16];
1711     char errbuf[ERRBUF_SIZE];
1712     
1713     daemon_assert(invariant(f));
1714     daemon_assert(cmd != NULL);
1715     daemon_assert(cmd->num_arg == 1);
1716
1717     /* create an absolute name for our file */
1718     file_name = cmd->arg[0].string;
1719     get_absolute_fname(full_path, sizeof(full_path), f->dir, file_name);
1720
1721     /* get the file information */
1722     if (!strncmp (full_path, "/dev", 4)
1723         && (!full_path[4] || full_path[4] == '/')) {
1724         errno = ENOENT;
1725         reply(f, 550, "Error getting file status; %s.",
1726               strerror_r(errno, errbuf, ERRBUF_SIZE));
1727     }
1728     else if (stat(full_path, &stat_buf) != 0) {
1729         reply(f, 550, "Error getting file status; %s.",
1730               strerror_r(errno, errbuf, ERRBUF_SIZE));
1731     } else {
1732         gmtime_r(&stat_buf.st_mtime, &mtime);
1733         strftime(time_buf, sizeof(time_buf), "%Y%m%d%H%M%S", &mtime);
1734         reply(f, 213, time_buf);
1735     }
1736
1737     daemon_assert(invariant(f));
1738 }
1739
1740
1741 static void send_readme(const ftp_session_t *f, int code)
1742 {
1743     char file_name[PATH_MAX+1];
1744     int dir_len;
1745     struct stat stat_buf;
1746     int fd;
1747     int read_ret;
1748     char buf[4096];
1749     char code_str[8];
1750     char *p;
1751     int len;
1752     char *nl;
1753     int line_len;
1754
1755     daemon_assert(invariant(f));
1756     daemon_assert(code >= 100);
1757     daemon_assert(code <= 559);
1758
1759     /* set up for early exit */
1760     fd = -1;
1761
1762     /* verify our README wouldn't be too long */
1763     dir_len = strlen(f->dir);
1764     if ((dir_len + 1 + sizeof(README_FILE_NAME)) > sizeof(file_name)) {
1765         goto exit_send_readme;
1766     }
1767
1768     /* create a README file name */
1769     strcpy(file_name, f->dir);
1770     strcat(file_name, "/");
1771     strcat(file_name, README_FILE_NAME);
1772
1773     /* open our file */
1774     fd = open(file_name, O_RDONLY);
1775     if (fd == -1) {
1776         goto exit_send_readme;
1777     }
1778
1779     /* verify this isn't a directory */
1780     if (fstat(fd, &stat_buf) != 0) {
1781         goto exit_send_readme;
1782     }
1783 #ifndef STATS_MACRO_BROKEN
1784     if (S_ISDIR(stat_buf.st_mode)) {
1785 #else
1786     if (!S_ISDIR(stat_buf.st_mode)) {
1787 #endif
1788         goto exit_send_readme;
1789     }
1790
1791     /* convert our code to a buffer */
1792     daemon_assert(code >= 100);
1793     daemon_assert(code <= 999);
1794     sprintf(code_str, "%03d-", code);
1795
1796     /* read and send */
1797     read_ret = read(fd, buf, sizeof(buf));
1798     if (read_ret > 0) {
1799         telnet_session_print(f->telnet_session, code_str);
1800         while (read_ret > 0) {
1801             p = buf;
1802             len = read_ret;
1803             nl = memchr(p, '\n', len);
1804             while ((len > 0) && (nl != NULL)) {
1805                 *nl = '\0';
1806                 telnet_session_println(f->telnet_session, p);
1807                 line_len = nl - p;
1808                 len -= line_len + 1;
1809                 if (len > 0) {
1810                     telnet_session_print(f->telnet_session, code_str);
1811                 }
1812                 p = nl+1;
1813                 nl = memchr(p, '\n', len);
1814             }
1815             if (len > 0) {
1816                 telnet_session_print(f->telnet_session, p);
1817             }
1818
1819             read_ret = read(fd, buf, sizeof(buf));
1820         }
1821     }
1822
1823     /* cleanup and exit */
1824 exit_send_readme:
1825     if (fd != -1) {
1826         close(fd);
1827     }
1828     daemon_assert(invariant(f));
1829 }
1830
1831 /* hack which prevents Netscape error in file list */
1832 static void netscape_hack(int fd)
1833 {
1834     fd_set readfds;
1835     struct timeval timeout;
1836     int select_ret;
1837     char c;
1838
1839     daemon_assert(fd >= 0);
1840
1841     shutdown(fd, 1);
1842     FD_ZERO(&readfds);
1843     FD_SET(fd, &readfds);
1844     timeout.tv_sec = 15;
1845     timeout.tv_usec = 0;
1846     select_ret = select(fd+1, &readfds, NULL, NULL, &timeout);
1847     if (select_ret > 0) {
1848         read(fd, &c, 1);
1849     }
1850 }
1851
1852 /* compare two addresses to see if they contain the same IP address */
1853 static int ip_equal(const sockaddr_storage_t *a, const sockaddr_storage_t *b)
1854 {
1855     daemon_assert(a != NULL);
1856     daemon_assert(b != NULL);
1857     daemon_assert((SSFAM(a) == AF_INET) || (SSFAM(a) == AF_INET6));
1858     daemon_assert((SSFAM(b) == AF_INET) || (SSFAM(b) == AF_INET6));
1859
1860     if (SSFAM(a) != SSFAM(b)) {
1861         return 0;
1862     }
1863     if (memcmp(&SINADDR(a), &SINADDR(b), sizeof(SINADDR(a))) != 0) {
1864         return 0;
1865     }
1866     return 1;
1867 }
1868