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