A couple of changes collected over the last year
[oftpd.git] / src / oftpd.c
1 /*
2  * $Id$
3  */
4
5 #include <config.h>
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <fcntl.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <pwd.h>
15 #include <syslog.h>
16 #include <pthread.h>
17 #include <stdlib.h>
18 #include <sys/socket.h>
19 #include <sys/un.h>
20
21 #include "oftpd.h"
22 #include "ftp_listener.h"
23 #include "error.h"
24
25 /* put our executable name here where everybody can see it */
26 static const char *exe_name = "oftpd";
27
28 int pasv_port_low = 1024;
29 int pasv_port_high = MAX_PORT;
30
31 static int my_syslog_fd = -1;
32
33 static void daemonize();
34 static void print_usage(const char *error);
35
36 int main(int argc, char *argv[])
37 {
38     int i;
39     
40     long num;
41     char *endptr;
42     int port;
43     int max_clients;
44
45     char *user_ptr;
46     char *dir_ptr;
47     char *address;
48     
49     char temp_buf[256];
50     char errbuf[ERRBUF_SIZE];
51
52     struct passwd *user_info;
53     error_t err;
54
55     ftp_listener_t ftp_listener;
56
57     int detach;
58
59     int log_facility;
60
61     sigset_t term_signal;
62     int sig;
63
64     /* grab our executable name */
65     if (argc > 0) {
66         exe_name = argv[0];
67     }
68
69     /* verify we're running as root */
70     if (geteuid() != 0) {
71         if ( argc > 1 && !strcmp (argv[1], "--version")) {
72                 printf ("%s %s\n", PACKAGE, VERSION);
73                 exit (0);
74         }
75         fprintf(stderr, "%s: program needs root permission to run\n", exe_name);
76         exit(1);
77
78     }
79
80     /* default command-line arguments */
81     port = FTP_PORT;
82     user_ptr = NULL;
83     dir_ptr = NULL;
84     address = FTP_ADDRESS;
85     max_clients = MAX_CLIENTS;
86     detach = 1;
87     log_facility = LOG_FTP;
88
89     /* check our command-line arguments */
90     /* we're stubbornly refusing to use getopt(), because we can */
91     /* :) */ 
92     for (i=1; i<argc; i++) {
93         
94         /* flags/optional arguments */
95         if (argv[i][0] == '-') {
96             if (strcmp(argv[i], "-p") == 0 
97                 || strcmp(argv[i], "--port") == 0) {
98                 if (++i >= argc) {
99                     print_usage("missing port number");
100                     exit(1);
101                 }
102                 num = strtol(argv[i], &endptr, 0);
103                 if ((num < MIN_PORT) || (num > MAX_PORT) || (*endptr != '\0')) {
104
105                     snprintf(temp_buf, sizeof(temp_buf), 
106                              "port must be a number between %d and %d",
107                              MIN_PORT, MAX_PORT);
108                     print_usage(temp_buf);
109
110                     exit(1);
111                 }
112                 port = num;
113             }
114             else if (strcmp(argv[i], "-r") == 0 
115                      || strcmp(argv[i], "--pasv-range") == 0) {
116                 i += 2;  
117                 if (i >= argc) {
118                     print_usage("invalid passive port range");
119                     exit(1);
120                 }
121                 num = strtol(argv[i-1], &endptr, 0);
122                 if ((num < 1024) || (num > MAX_PORT) || (*endptr != '\0')) {
123
124                     snprintf(temp_buf, sizeof(temp_buf), 
125                              "low passive port must be a number between 1024 and %d",
126                              MAX_PORT);
127                     print_usage(temp_buf);
128
129                     exit(1);
130                 }
131                 pasv_port_low = num;
132                 num = strtol(argv[i], &endptr, 0);
133                 if ((num < pasv_port_low) || (num > MAX_PORT) || (*endptr != '\0')) {
134
135                     snprintf(temp_buf, sizeof(temp_buf), 
136                              "high passive port must be a number between %d and %d",
137                              pasv_port_low, MAX_PORT);
138                     print_usage(temp_buf);
139
140                     exit(1);
141                 }
142                 pasv_port_high = num;
143             } else if (strcmp(argv[i], "-h") == 0
144                        || strcmp(argv[i], "--help") == 0) {
145                 print_usage(NULL);
146                 exit(0);
147             } else if (strcmp(argv[i], "-i") == 0
148                        || strcmp(argv[i], "--interface") == 0) {
149                 if (++i >= argc) {
150                     print_usage("missing interface");
151                     exit(1);
152                 }
153                 address = argv[i];
154             } else if (strcmp(argv[i], "-m") == 0
155                        || strcmp(argv[i], "--max-clients") == 0) {
156                 if (++i >= argc) {
157                     print_usage("missing number of max clients");
158                     exit(1);
159                 }
160                 num = strtol(argv[i], &endptr, 0);
161                 if ((num < MIN_NUM_CLIENTS) || (num > MAX_NUM_CLIENTS) 
162                     || (*endptr != '\0')) {
163
164                     snprintf(temp_buf, sizeof(temp_buf),
165                         "max clients must be a number between %d and %d",
166                         MIN_NUM_CLIENTS, MAX_NUM_CLIENTS);
167                     print_usage(temp_buf);
168
169                     exit(1);
170                 }
171                 max_clients = num;
172             } else if (strcmp(argv[i], "-N") == 0 
173                        || strcmp(argv[i], "--nodetach") == 0) {
174                 detach = 0;
175             } else if (strcmp(argv[i], "-l") == 0
176                        || strcmp(argv[i], "--local") == 0) {
177                 if (++i >= argc) {
178                     print_usage("missing number for local facility logging");
179                     exit(1);
180                 }
181                 switch (argv[i][0]) {
182                     case '0': 
183                         log_facility = LOG_LOCAL0;
184                         break;
185                     case '1': 
186                         log_facility = LOG_LOCAL1;
187                         break;
188                     case '2': 
189                         log_facility = LOG_LOCAL2;
190                         break;
191                     case '3': 
192                         log_facility = LOG_LOCAL3;
193                         break;
194                     case '4': 
195                         log_facility = LOG_LOCAL4;
196                         break;
197                     case '5': 
198                         log_facility = LOG_LOCAL5;
199                         break;
200                     case '6': 
201                         log_facility = LOG_LOCAL6;
202                         break;
203                     case '7': 
204                         log_facility = LOG_LOCAL7;
205                         break;
206                 }
207             } else if (strcmp(argv[i], "--version") == 0) {
208                 printf ("%s %s\n", PACKAGE, VERSION);
209                 exit (0);
210             } else {
211                 print_usage("unknown option");
212                 exit(1);
213             }
214
215         /* required parameters */
216         } else {
217             if (user_ptr == NULL) {
218                 user_ptr = argv[i];
219             } else if (dir_ptr == NULL) {
220                 dir_ptr = argv[i];
221             } else {
222                 print_usage("too many arguments on the command line");
223                 exit(1);
224             }
225         }
226     }
227     if ((user_ptr == NULL) || (dir_ptr == NULL)) {
228         print_usage("missing user and/or directory name");
229         exit(1);
230     }
231
232     user_info = getpwnam(user_ptr);
233     if (user_info == NULL) {
234         fprintf(stderr, "%s: invalid user name\n", exe_name);
235         exit(1);
236     }
237
238     /* become a daemon */
239     if (detach) {
240         daemonize();
241     }
242
243     /* avoid SIGPIPE on socket activity */
244     signal(SIGPIPE, SIG_IGN);         
245
246     /* log the start time */
247     openlog(NULL, LOG_NDELAY, log_facility);
248     syslog(LOG_INFO,"Starting, version %s, as PID %d", VERSION, getpid());
249
250     /* change to root directory */
251     if (chroot(dir_ptr) != 0) {
252         syslog(LOG_ERR, "error with root directory; %s\n", exe_name, 
253           strerror_r(errno, errbuf, ERRBUF_SIZE));
254         exit(1);
255     }
256     if (chdir("/") != 0) {
257         syslog(LOG_ERR, "error changing directory; %s\n",
258                strerror_r(errno, errbuf, ERRBUF_SIZE));
259         exit(1);
260     }
261
262     /* Check that a /dev directory exists, so that syslog works
263        properly even after a syslogd restart. */
264     {
265       struct stat stat_buf;
266
267       if (stat( "/dev", &stat_buf)) {
268         syslog (LOG_ERR, "required `%s/dev' directory is missing: %s\n",
269                 dir_ptr, strerror_r (errno, errbuf, ERRBUF_SIZE));
270       }
271 #ifndef STATS_MACRO_BROKEN
272       if (!S_ISDIR(stat_buf.st_mode)) {
273 #else
274       if (S_ISDIR(stat_buf.st_mode)) {
275 #endif
276           syslog (LOG_ERR, "`%s/dev' is not a directory\n", dir_ptr);
277       }
278     }
279
280
281     /* create our main listener */
282     if (!ftp_listener_init(&ftp_listener, 
283                            address,
284                            port,
285                            max_clients,
286                            INACTIVITY_TIMEOUT, 
287                            &err)) 
288     {
289         syslog(LOG_ERR, "error initializing FTP listener; %s",
290           error_get_desc(&err));
291         exit(1);
292     }
293
294     /* set user to be as inoffensive as possible */
295     if (setgid(user_info->pw_gid) != 0) {
296         syslog(LOG_ERR, "error changing group; %s",
297                strerror_r(errno, errbuf, ERRBUF_SIZE));
298         exit(1);
299     }
300     if (setuid(user_info->pw_uid) != 0) {
301         syslog(LOG_ERR, "error changing group; %s",
302                strerror_r(errno, errbuf, ERRBUF_SIZE));
303         exit(1);
304     }
305
306     /* start our listener */
307     if (ftp_listener_start(&ftp_listener, &err) == 0) {
308         syslog(LOG_ERR, "error starting FTP service; %s", error_get_desc(&err));
309         exit(1);
310     }
311
312     /* wait for a SIGTERM and exit gracefully */
313     sigemptyset(&term_signal);
314     sigaddset(&term_signal, SIGTERM);
315     sigaddset(&term_signal, SIGINT);
316     pthread_sigmask(SIG_BLOCK, &term_signal, NULL);
317     sigwait(&term_signal, &sig);
318     if (sig == SIGTERM) {
319         syslog(LOG_INFO, "SIGTERM received, shutting down");
320     } else { 
321         syslog(LOG_INFO, "SIGINT received, shutting down");
322     }
323     ftp_listener_stop(&ftp_listener);
324     syslog(LOG_INFO, "all connections finished, FTP server exiting");
325     exit(0);
326 }
327
328 static void print_usage(const char *error)
329 {
330     if (error != NULL) {
331         fprintf(stderr, "%s: %s\n", exe_name, error);
332     }
333     fprintf(stderr, 
334            " Syntax: %s [ options... ] user_name root_directory\n", exe_name);
335     fprintf(stderr, 
336            " Options:\n"
337            " -p, --port <num>\n"
338            "     Set the port to listen on (Default: %d)\n"
339            " -i, --interface <IP Address>\n"
340            "     Set the interface to listen on (Default: all)\n"
341            " -m, --max-clients <num>\n"
342            "     Set the number of clients allowed at one time (Default: %d)\n"
343            "-l, --local <local-logging>\n"
344            "     Use LOCAL facility for syslog, local-logging is 0 to 7\n"
345            " -N, --nodetach\n"
346            "     Do not detach from TTY and become a daemon\n",
347            DEFAULT_FTP_PORT, MAX_CLIENTS);
348 }
349
350 static void daemonize()
351 {
352     int fork_ret;
353     int max_fd;
354     int null_fd;
355     int fd;
356     char errbuf[ERRBUF_SIZE];
357
358     null_fd = open("/dev/null", O_RDWR);
359     if (null_fd == -1) {
360         fprintf(stderr, "%s: error opening null output device; %s\n", exe_name, 
361           strerror_r(errno, errbuf, ERRBUF_SIZE));
362         exit(1);
363     }
364
365     max_fd = sysconf(_SC_OPEN_MAX);
366     if (max_fd == -1) {
367         fprintf(stderr, "%s: error getting maximum open file; %s\n", exe_name, 
368           strerror_r(errno, errbuf, ERRBUF_SIZE));
369         exit(1);
370     }
371
372
373     fork_ret = fork();
374     if (fork_ret == -1) {
375         fprintf(stderr, "%s: error forking; %s\n",
376                 exe_name, strerror_r(errno, errbuf, ERRBUF_SIZE));
377         exit(1);
378     }
379     if (fork_ret != 0) {
380         exit(0);
381     }
382     if (setsid() == -1) {
383         fprintf(stderr, "%s: error creating process group; %s\n", exe_name, 
384           strerror_r(errno, errbuf, ERRBUF_SIZE));
385         exit(1);
386     }
387     fork_ret = fork();
388     if (fork_ret == -1) {
389         fprintf(stderr, "%s: error forking; %s\n",
390                 exe_name, strerror_r(errno, errbuf, ERRBUF_SIZE));
391         exit(1);
392     }
393     if (fork_ret != 0) {
394         exit(0);
395     }
396     if (dup2(null_fd, 0) == -1) {
397         syslog(LOG_ERR, "error setting input to null; %s", 
398           strerror_r(errno, errbuf, ERRBUF_SIZE));
399         exit(1);
400     }
401     if (dup2(null_fd, 1) == -1) {
402         syslog(LOG_ERR, "error setting output to null; %s", 
403           strerror_r(errno, errbuf, ERRBUF_SIZE));
404         exit(1);
405     }
406     if (dup2(null_fd, 2) == -1) {
407         syslog(LOG_ERR, "error setting error output to null; %s", 
408           strerror_r(errno, errbuf, ERRBUF_SIZE));
409         exit(1);
410     }
411     for (fd=3; fd<max_fd; fd++) {
412         close(fd);
413     }
414 }
415