/*
 * $Id: oftpd.c,v 1.20 2001/05/27 22:40:18 shane Exp $
 */

#include <config.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <pwd.h>
#include <syslog.h>
#include <pthread.h>
#include <stdlib.h>
#include "oftpd.h"
#include "ftp_listener.h"
#include "error.h"

/* put our executable name here where everybody can see it */
static const char *exe_name = "oftpd";

static void daemonize();
static void print_usage(const char *error);

int main(int argc, char *argv[])
{
    int i;
    
    long num;
    char *endptr;
    int port;
    int max_clients;
    int log_facility;

    char *user_ptr;
    char *dir_ptr;
    char *address;
    
    char temp_buf[256];

    struct passwd *user_info;
    error_t err;

    ftp_listener_t ftp_listener;

    int detach;

    sigset_t term_signal;
    int sig;

    /* grab our executable name */
    if (argc > 0) {
        exe_name = argv[0];
    }

    /* verify we're running as root */
    if (geteuid() != 0) {
        fprintf(stderr, "%s: program needs root permission to run\n", exe_name);
        exit(1);
    }

    /* default command-line arguments */
    port = FTP_PORT;
    user_ptr = NULL;
    dir_ptr = NULL;
    address = FTP_ADDRESS;
    max_clients = MAX_CLIENTS;
    detach = 1;
    log_facility = LOG_FTP;

    /* check our command-line arguments */
    /* we're stubbornly refusing to use getopt(), because we can */
    /* :) */ 
    for (i=1; i<argc; i++) {
        
        /* flags/optional arguments */
        if (argv[i][0] == '-') {
            if (strcmp(argv[i], "-p") == 0 
                || strcmp(argv[i], "--port") == 0) {
                if (++i >= argc) {
                    print_usage("missing port number");
                    exit(1);
                }
                num = strtol(argv[i], &endptr, 0);
                if ((num < MIN_PORT) || (num > MAX_PORT) || (*endptr != '\0')) {

                    snprintf(temp_buf, sizeof(temp_buf), 
                             "port must be a number between %d and %d",
                             MIN_PORT, MAX_PORT);
                    print_usage(temp_buf);

                    exit(1);
                }
                port = num;
            } else if (strcmp(argv[i], "-h") == 0
                       || strcmp(argv[i], "--help") == 0) {
                print_usage(NULL);
                exit(0);
            } else if (strcmp(argv[i], "-i") == 0
                       || strcmp(argv[i], "--interface") == 0) {
                if (++i >= argc) {
                    print_usage("missing interface");
                    exit(1);
                }
                address = argv[i];
            } else if (strcmp(argv[i], "-m") == 0
                       || strcmp(argv[i], "--max-clients") == 0) {
                if (++i >= argc) {
                    print_usage("missing number of max clients");
                    exit(1);
                }
                num = strtol(argv[i], &endptr, 0);
                if ((num < MIN_NUM_CLIENTS) || (num > MAX_NUM_CLIENTS) 
                    || (*endptr != '\0')) {

                    snprintf(temp_buf, sizeof(temp_buf),
                        "max clients must be a number between %d and %d",
                        MIN_NUM_CLIENTS, MAX_NUM_CLIENTS);
                    print_usage(temp_buf);

                    exit(1);
                }
                max_clients = num;
            } else if (strcmp(argv[i], "-N") == 0 
                       || strcmp(argv[i], "--nodetach") == 0) {
                detach = 0;
            } else if (strcmp(argv[i], "-l") == 0
                       || strcmp(argv[i], "--local") == 0) {
                if (++i >= argc) {
                    print_usage("missing number for local facility logging");
                    exit(1);
                }
                switch (argv[i][0]) {
                    case '0': 
                        log_facility = LOG_LOCAL0;
                        break;
                    case '1': 
                        log_facility = LOG_LOCAL1;
                        break;
                    case '2': 
                        log_facility = LOG_LOCAL2;
                        break;
                    case '3': 
                        log_facility = LOG_LOCAL3;
                        break;
                    case '4': 
                        log_facility = LOG_LOCAL4;
                        break;
                    case '5': 
                        log_facility = LOG_LOCAL5;
                        break;
                    case '6': 
                        log_facility = LOG_LOCAL6;
                        break;
                    case '7': 
                        log_facility = LOG_LOCAL7;
                        break;
                }
            } else {
                print_usage("unknown option");
                exit(1);
            }

        /* required parameters */
        } else {
            if (user_ptr == NULL) {
                user_ptr = argv[i];
            } else if (dir_ptr == NULL) {
                dir_ptr = argv[i];
            } else {
                print_usage("too many arguments on the command line");
                exit(1);
            }
        }
    }
    if ((user_ptr == NULL) || (dir_ptr == NULL)) {
        print_usage("missing user and/or directory name");
        exit(1);
    }

    user_info = getpwnam(user_ptr);
    if (user_info == NULL) {
        fprintf(stderr, "%s: invalid user name\n", exe_name);
        exit(1);
    }

    /* become a daemon */
    if (detach) {
        daemonize();
    }

    /* avoid SIGPIPE on socket activity */
    signal(SIGPIPE, SIG_IGN);         

    /* log the start time */
    openlog(NULL, LOG_NDELAY, log_facility);
    syslog(LOG_INFO,"Starting, version %s, as PID %d", VERSION, getpid());

    /* change to root directory */
    if (chroot(dir_ptr) != 0) {
        syslog(LOG_ERR, "error with root directory; %s\n", exe_name, 
          strerror(errno));
        exit(1);
    }
    if (chdir("/") != 0) {
        syslog(LOG_ERR, "error changing directory; %s\n", strerror(errno));
        exit(1);
    }

    /* create our main listener */
    if (!ftp_listener_init(&ftp_listener, 
                           address,
                           port,
                           max_clients,
                           INACTIVITY_TIMEOUT, 
                           &err)) 
    {
        syslog(LOG_ERR, "error initializing FTP listener; %s",
          error_get_desc(&err));
        exit(1);
    }

    /* set user to be as inoffensive as possible */
    if (setgid(user_info->pw_gid) != 0) {
        syslog(LOG_ERR, "error changing group; %s", strerror(errno));
        exit(1);
    }
    if (setuid(user_info->pw_uid) != 0) {
        syslog(LOG_ERR, "error changing group; %s", strerror(errno));
        exit(1);
    }

    /* start our listener */
    if (ftp_listener_start(&ftp_listener, &err) == 0) {
        syslog(LOG_ERR, "error starting FTP service; %s", error_get_desc(&err));
        exit(1);
    }

    /* wait for a SIGTERM and exit gracefully */
    sigemptyset(&term_signal);
    sigaddset(&term_signal, SIGTERM);
    sigaddset(&term_signal, SIGINT);
    pthread_sigmask(SIG_BLOCK, &term_signal, NULL);
    sigwait(&term_signal, &sig);
    if (sig == SIGTERM) {
        syslog(LOG_INFO, "SIGTERM received, shutting down");
    } else { 
        syslog(LOG_INFO, "SIGINT received, shutting down");
    }
    ftp_listener_stop(&ftp_listener);
    syslog(LOG_INFO, "all connections finished, FTP server exiting");
    exit(0);
}

static void print_usage(const char *error)
{
    if (error != NULL) {
        fprintf(stderr, "%s: %s\n", exe_name, error);
    }
    fprintf(stderr, 
           " Syntax: %s [ options... ] user_name root_directory\n", exe_name);
    fprintf(stderr, 
           " Options:\n"
           " -p, --port <num>\n"
           "     Set the port to listen on (Default: %d)\n"
           " -i, --interface <IP Address>\n"
           "     Set the interface to listen on (Default: all)\n"
           " -m, --max-clients <num>\n"
           "     Set the number of clients allowed at one time (Default: %d)\n"
           "-l, --local <local-logging>\n"
           "     Use LOCAL facility for syslog, local-logging is 0 to 7\n"
           " -N, --nodetach\n"
           "     Do not detach from TTY and become a daemon\n",
           DEFAULT_FTP_PORT, MAX_CLIENTS);
}

static void daemonize()
{
    int fork_ret;
    int max_fd;
    int null_fd;
    int fd;

    null_fd = open("/dev/null", O_RDWR);
    if (null_fd == -1) {
        fprintf(stderr, "%s: error opening null output device; %s\n", exe_name, 
          strerror(errno));
        exit(1);
    }

    max_fd = sysconf(_SC_OPEN_MAX);
    if (max_fd == -1) {
        fprintf(stderr, "%s: error getting maximum open file; %s\n", exe_name, 
          strerror(errno));
        exit(1);
    }


    fork_ret = fork();
    if (fork_ret == -1) {
        fprintf(stderr, "%s: error forking; %s\n", exe_name, strerror(errno));
        exit(1);
    }
    if (fork_ret != 0) {
        exit(0);
    }
    if (setsid() == -1) {
        fprintf(stderr, "%s: error creating process group; %s\n", exe_name, 
          strerror(errno));
        exit(1);
    }
    fork_ret = fork();
    if (fork_ret == -1) {
        fprintf(stderr, "%s: error forking; %s\n", exe_name, strerror(errno));
        exit(1);
    }
    if (fork_ret != 0) {
        exit(0);
    }
    if (dup2(null_fd, 0) == -1) {
        syslog(LOG_ERR, "error setting input to null; %s", 
          strerror(errno));
        exit(1);
    }
    if (dup2(null_fd, 1) == -1) {
        syslog(LOG_ERR, "error setting output to null; %s", 
          strerror(errno));
        exit(1);
    }
    if (dup2(null_fd, 2) == -1) {
        syslog(LOG_ERR, "error setting error output to null; %s", 
          strerror(errno));
        exit(1);
    }
    for (fd=3; fd<max_fd; fd++) {
        close(fd);
    }
}



syntax highlighted by Code2HTML, v. 0.9.1