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