<commit_before>/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* @version $Id$ */
#include "jsvc.h"

#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <errno.h>
#ifdef OS_LINUX
#include <sys/prctl.h>
#include <sys/syscall.h>
#define _LINUX_FS_H
#include <linux/capability.h>
#ifdef HAVE_LIBCAP
#include <sys/capability.h>
#endif
#endif
#include <time.h>

#ifdef OS_CYGWIN
#include <sys/fcntl.h>
#define F_ULOCK 0               /* Unlock a previously locked region */
#define F_LOCK  1               /* Lock a region for exclusive use */
#endif

extern char **environ;

static mode_t envmask;          /* mask to create the files */

pid_t controlled = 0;           /* the child process pid */
pid_t logger_pid = 0;           /* the logger process pid */
static bool stopping = false;
static bool doreload = false;
static bool doreopen = false;
typedef void (*sighandler_t)(int);
static sighandler_t handler_int  = NULL;
static sighandler_t handler_usr1 = NULL;
static sighandler_t handler_hup  = NULL;
static sighandler_t handler_trm  = NULL;

static int run_controller(arg_data *args, home_data *data, uid_t uid,
                          gid_t gid);
static void set_output(char *outfile, char *errfile, bool redirectstdin,
                       char *procname);

#ifdef OS_CYGWIN
/*
 * File locking routine
 */
static int lockf(int fildes, int function, off_t size)
{
    struct flock buf;

    switch (function) {
    case F_LOCK:
        buf.l_type = F_WRLCK;
        break;
    case F_ULOCK:
        buf.l_type = F_UNLCK;
        break;
    default:
        return -1;
    }
    buf.l_whence = 0;
    buf.l_start = 0;
    buf.l_len = size;

    return fcntl(fildes, F_SETLK, &buf);
}

#endif

static void handler(int sig)
{
    switch (sig) {
        case SIGTERM:
            log_debug("Caught SIGTERM: Scheduling a shutdown");
            if (stopping == true) {
                log_error("Shutdown or reload already scheduled");
            }
            else {
                stopping = true;
            }
        break;
        case SIGINT:
            log_debug("Caught SIGINT: Scheduling a shutdown");
            if (stopping == true) {
                log_error("Shutdown or reload already scheduled");
            }
            else {
                stopping = true;
            }
        break;
        case SIGHUP:
            log_debug("Caught SIGHUP: Scheduling a reload");
            if (stopping == true) {
                log_error("Shutdown or reload already scheduled");
            }
            else {
                stopping = true;
                doreload = true;
            }
        break;
        case SIGUSR1:
             log_debug("Caught SIGUSR1: Reopening logs");
             doreopen = true;
        break;
        default:
            log_debug("Caught unknown signal %d", sig);
        break;
    }
}

/* user and group */
static int set_user_group(char *user, int uid, int gid)
{
    if (user != NULL) {
        if (setgid(gid) != 0) {
            log_error("Cannot set group id for user '%s'", user);
            return -1;
        }
        if (initgroups(user, gid) != 0) {
            if (getuid() != uid) {
                log_error("Cannot set supplement group list for user '%s'",
                          user);
                return -1;
            }
            else
                log_debug("Cannot set supplement group list for user '%s'",
                          user);
        }
        if (getuid() == uid) {
            log_debug("No need to change user to '%s'!", user);
            return 0;
        }
        if (setuid(uid) != 0) {
            log_error("Cannot set user id for user '%s'", user);
            return -1;
        }
        log_debug("user changed to '%s'", user);
    }
    return 0;
}

/* Set linux capability, user and group */
#ifdef OS_LINUX
#ifdef HAVE_LIBCAP
static cap_value_t caps_std[] = {
    CAP_NET_BIND_SERVICE,
    CAP_SETUID,
    CAP_SETGID,
    CAP_DAC_READ_SEARCH,
    CAP_DAC_OVERRIDE
};

static cap_value_t caps_min[] = {
    CAP_NET_BIND_SERVICE,
    CAP_DAC_READ_SEARCH,
    CAP_DAC_OVERRIDE
};

#define CAPS     1
#define CAPSMIN  2

static int set_caps(int cap_type)
{
    cap_t c;
    int ncap;
    cap_value_t *caps;

    if (cap_type == CAPS) {
        ncap = sizeof(caps_std)/sizeof(cap_value_t);
        caps = caps_std;
    }
    else {
        ncap = sizeof(caps_min)/sizeof(cap_value_t);
        caps = caps_min;
    }
    c = cap_init();
    cap_clear(c);
    cap_set_flag(c, CAP_EFFECTIVE,   ncap, caps, CAP_SET);
    cap_set_flag(c, CAP_INHERITABLE, ncap, caps, CAP_SET);
    cap_set_flag(c, CAP_PERMITTED,   ncap, caps, CAP_SET);
    if (cap_set_proc(c) != 0) {
        log_error("failed setting %s capabilities.",
                  cap_type == CAPS ? "default" : "min");
        return -1;
    }
    cap_free(c);
    if (cap_type == CAPS)
        log_debug("increased capability set.");
    else
        log_debug("decreased capability set to min required.");
    return 0;
}

#else /* !HAVE_LIBCAP */
/* CAPSALL is to allow to read/write at any location */
#define CAPSALL (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_SETUID) +             \
                (1 << CAP_SETGID) +             \
                (1 << CAP_DAC_READ_SEARCH) +    \
                (1 << CAP_DAC_OVERRIDE)

#define CAPSMAX (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_DAC_READ_SEARCH) +    \
                (1 << CAP_DAC_OVERRIDE)

/* That a more reasonable configuration */
#define CAPS    (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_DAC_READ_SEARCH) +    \
                (1 << CAP_SETUID) +             \
                (1 << CAP_SETGID)

/* probably the only one Java could use */
#define CAPSMIN (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_DAC_READ_SEARCH)

#define LEGACY_CAP_VERSION  0x19980330
static int set_caps(int caps)
{
    struct __user_cap_header_struct caphead;
    struct __user_cap_data_struct   cap;

    memset(&caphead, 0, sizeof caphead);
    caphead.version = LEGACY_CAP_VERSION;
    caphead.pid = 0;
    memset(&cap, 0, sizeof cap);
    cap.effective = caps;
    cap.permitted = caps;
    cap.inheritable = caps;
    if (syscall(__NR_capset, &caphead, &cap) < 0) {
        log_error("set_caps: failed to set capabilities");
        log_error("check that your kernel supports capabilities");
        return -1;
    }
    return 0;
}
#endif

static int linuxset_user_group(char *user, int uid, int gid)
{
    int caps_set = 0;
    /* set capabilities enough for binding port 80 setuid/getuid */
    if (getuid() == 0) {
        if (set_caps(CAPS) != 0) {
            if (getuid() != uid) {
                log_error("set_caps(CAPS) failed");
                return -1;
            }
            log_debug("set_caps(CAPS) failed");
        }
        /* make sure they are kept after setuid */
        if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
            log_error("prctl failed in linuxset_user_group");
            return -1;
        }
        caps_set = 1;
    }

    /* set setuid/getuid */
    if (set_user_group(user, uid, gid) != 0) {
        log_error("set_user_group failed in linuxset_user_group");
        return -1;
    }

    if (caps_set) {
        /* set capability to binding port 80 read conf */
        if (set_caps(CAPSMIN) != 0) {
            if (getuid() != uid) {
                log_error("set_caps(CAPSMIN) failed");
                return -1;
            }
            log_debug("set_caps(CAPSMIN) failed");
        }
    }

    return 0;
}
#endif


static bool checkuser(char *user, uid_t * uid, gid_t * gid)
{
    struct passwd *pwds = NULL;
    int status = 0;
    pid_t pid  = 0;

    /* Do we actually _have_ to switch user? */
    if (user == NULL)
        return true;

    pwds = getpwnam(user);
    if (pwds == NULL) {
        log_error("Invalid user name '%s' specified", user);
        return false;
    }

    *uid = pwds->pw_uid;
    *gid = pwds->pw_gid;

    /* Validate the user name in another process */
    pid = fork();
    if (pid == -1) {
        log_error("Cannot validate user name");
        return false;
    }

    /* If we're in the child process, let's validate */
    if (pid == 0) {
        if (set_user_group(user, *uid, *gid) != 0)
            exit(1);
        /* If we got here we switched user/group */
        exit(0);
    }

    while (waitpid(pid, &status, 0) != pid) {
        /* Just wait */
    }

    /* The child must have exited cleanly */
    if (WIFEXITED(status)) {
        status = WEXITSTATUS(status);

        /* If the child got out with 0 the user is ok */
        if (status == 0) {
            log_debug("User '%s' validated", user);
            return true;
        }
    }

    log_error("Error validating user '%s'", user);
    return false;
}

#ifdef OS_CYGWIN
static void cygwincontroller(void)
{
    raise(SIGTERM);
}
#endif
static void controller(int sig)
{
    switch (sig) {
        case SIGTERM:
        case SIGINT:
        case SIGHUP:
        case SIGUSR1:
            log_debug("Forwarding signal %d to process %d", sig, controlled);
            kill(controlled, sig);
            signal(sig, controller);
        break;
        default:
            log_debug("Caught unknown signal %d", sig);
        break;
    }
}

/*
 * Return the address of the current signal handler and set the new one.
 */
static sighandler_t signal_set(int sig, sighandler_t newHandler)
{
    sighandler_t hand;

    hand = signal(sig, newHandler);
#ifdef SIG_ERR
    if (hand == SIG_ERR)
        hand = NULL;
#endif
    if (hand == handler || hand == controller)
        hand = NULL;
    return hand;
}

/*
 * Check pid and if still running
 */

static int check_pid(arg_data *args)
{
    int fd;
    FILE *pidf;
    char buff[80];
    pid_t pidn = getpid();
    int i, pid;

    fd = open(args->pidf, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        log_error("Cannot open PID file %s, PID is %d", args->pidf, pidn);
        return -1;
    }
    else {
        lockf(fd, F_LOCK, 0);
        i = read(fd, buff, sizeof(buff));
        if (i > 0) {
            buff[i] = '\0';
            pid = atoi(buff);
            if (kill(pid, 0) == 0) {
                log_error("Still running according to PID file %s, PID is %d",
                          args->pidf, pid);
                lockf(fd, F_ULOCK, 0);
                close(fd);
                return 122;
            }
        }

        /* skip writing the pid file if version or check */
        if (args->vers != true && args->chck != true) {
            lseek(fd, SEEK_SET, 0);
            pidf = fdopen(fd, "r+");
            fprintf(pidf, "%d\n", (int)getpid());
            fflush(pidf);
            lockf(fd, F_ULOCK, 0);
            fclose(pidf);
            close(fd);
        }
        else {
            lockf(fd, F_ULOCK, 0);
            close(fd);
        }
    }
    return 0;
}

/*
 * read the pid from the pidfile
 */
static int get_pidf(arg_data *args)
{
    int fd;
    int i;
    char buff[80];

    fd = open(args->pidf, O_RDONLY, 0);
    log_debug("get_pidf: %d in %s", fd, args->pidf);
    if (fd < 0) {
        /* something has gone wrong the JVM has stopped */
        return -1;
    }
    lockf(fd, F_LOCK, 0);
    i = read(fd, buff, sizeof(buff));
    lockf(fd, F_ULOCK, 0);
    close(fd);
    if (i > 0) {
        buff[i] = '\0';
        i = atoi(buff);
        log_debug("get_pidf: pid %d", i);
        if (kill(i, 0) == 0)
            return i;
    }
    return -1;
}

/*
 * Check temporatory file created by controller
 * /tmp/pid.jsvc_up
 * Notes:
 * we fork several times
 * 1 - to be a daemon before the setsid(), the child is the controler process.
 * 2 - to start the JVM in the child process. (whose pid is stored in pidfile).
 */
static int check_tmp_file(arg_data *args)
{
    int pid;
    char buff[80];
    int fd;

    pid = get_pidf(args);
    if (pid < 0)
        return -1;
    sprintf(buff, "/tmp/%d.jsvc_up", pid);
    log_debug("check_tmp_file: %s", buff);
    fd = open(buff, O_RDONLY);
    if (fd == -1)
        return -1;
    close(fd);
    return 0;
}

static void create_tmp_file(arg_data *args)
{
    char buff[80];
    int fd;

    sprintf(buff, "/tmp/%d.jsvc_up", (int)getpid());
    log_debug("create_tmp_file: %s", buff);
    fd = open(buff, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd != -1)
        close(fd);
}

static void remove_tmp_file(arg_data *args)
{
    char buff[80];

    sprintf(buff, "/tmp/%d.jsvc_up", (int)getpid());
    log_debug("remove_tmp_file: %s", buff);
    unlink(buff);
}

/*
 * wait until jsvc create the I am ready file
 * pid is the controller and args->pidf the JVM itself.
 */
static int wait_child(arg_data *args, int pid)
{
    int count = 10;
    bool havejvm = false;
    int fd;
    char buff[80];
    int i, status, waittime;

    log_debug("wait_child %d", pid);
    waittime = args->wait / 10;
    if (waittime > 10) {
        count = waittime;
        waittime = 10;
    }
    while (count > 0) {
        sleep(1);
        /* check if the controler is still running */
        if (waitpid(pid, &status, WNOHANG) == pid) {
            if (WIFEXITED(status))
                return (WEXITSTATUS(status));
            else
                return 1;
        }

        /* check if the pid file process exists */
        fd = open(args->pidf, O_RDONLY);
        if (fd < 0 && havejvm) {
            /* something has gone wrong the JVM has stopped */
            return 1;
        }
        lockf(fd, F_LOCK, 0);
        i = read(fd, buff, sizeof(buff));
        lockf(fd, F_ULOCK, 0);
        close(fd);
        if (i > 0) {
            buff[i] = '\0';
            i = atoi(buff);
            if (kill(i, 0) == 0) {
                /* the JVM process has started */
                havejvm = true;
                if (check_tmp_file(args) == 0) {
                    /* the JVM is started */
                    if (waitpid(pid, &status, WNOHANG) == pid) {
                        if (WIFEXITED(status))
                            return (WEXITSTATUS(status));
                        else
                            return 1;
                    }
                    return 0; /* ready JVM started */
                }
            }
        }
        sleep(waittime);
        count--;
    }
    /* It takes more than the wait time to start,
     * something must be wrong
     */
    return 1;
}

/*
 * stop the running jsvc
 */
static int stop_child(arg_data *args)
{
    int pid = get_pidf(args);
    int count = 10;

    if (pid > 0) {
        /* kill the process and wait until the pidfile has been
         * removed by the controler
         */
        kill(pid, SIGTERM);
        while (count > 0) {
            sleep(6);
            pid = get_pidf(args);
            if (pid <= 0) {
                /* JVM has stopped */
                return 0;
            }
            count--;
        }
    }
    return -1;
}

/*
 * child process logic.
 */

static int child(arg_data *args, home_data *data, uid_t uid, gid_t gid)
{
    int ret = 0;

    /* check the pid file */
    ret = check_pid(args);
    if (args->vers != true && args->chck != true) {
        if (ret == 122)
            return ret;
        if (ret < 0)
            return ret;
    }

    /* create a new process group to prevent kill 0 killing the monitor process */
#if defined(OS_FREEBSD) || defined(OS_DARWIN)
    setpgid(0, 0);
#else
    setpgrp();
#endif

#ifdef OS_LINUX
    /* setuid()/setgid() only apply the current thread so we must do it now */
    if (linuxset_user_group(args->user, uid, gid) != 0)
        return 4;
#endif
    /* Initialize the Java VM */
    if (java_init(args, data) != true) {
        log_debug("java_init failed");
        return 1;
    }
    else
        log_debug("java_init done");

    /* Check wether we need to dump the VM version */
    if (args->vers == true) {
        if (java_version() != true) {
            return -1;
        }
        else
            return 0;
    }
    /* Check wether we need to dump the VM version */
    else if (args->vershow == true) {
        if (java_version() != true) {
            return 7;
        }
    }

    /* Do we have to do a "check-only" initialization? */
    if (args->chck == true) {
        if (java_check(args) != true)
            return 2;
        printf("Service \"%s\" checked successfully\n", args->clas);
        return 0;
    }

    /* Load the service */
    if (java_load(args) != true) {
        log_debug("java_load failed");
        return 3;
    }
    else
        log_debug("java_load done");

    /* Downgrade user */
#ifdef OS_LINUX
    if (set_caps(0) != 0) {
        log_debug("set_caps (0) failed");
        return 4;
    }
#else
    if (set_user_group(args->user, uid, gid) != 0)
        return 4;
#endif

    /* Start the service */
    umask(envmask);
    if (java_start() != true) {
        log_debug("java_start failed");
        return 5;
    }
    else
        log_debug("java_start done");

    /* Install signal handlers */
    handler_hup = signal_set(SIGHUP, handler);
    handler_usr1 = signal_set(SIGUSR1, handler);
    handler_trm = signal_set(SIGTERM, handler);
    handler_int = signal_set(SIGINT, handler);
    controlled = getpid();

    log_debug("Waiting for a signal to be delivered");
    create_tmp_file(args);
    while (!stopping) {
#if defined(OSD_POSIX) || defined(HAVE_KAFFEVM)
        java_sleep(60);
        /* pause(); */
#else
        /* pause() is not threadsafe */
        sleep(60);
#endif
        if(doreopen) {
            doreopen = false;
            set_output(args->outfile, args->errfile, args->redirectstdin, args->procname);
        }
    }
    remove_tmp_file(args);
    log_debug("Shutdown or reload requested: exiting");

    /* Stop the service */
    if (java_stop() != true)
        return 6;

    if (doreload == true)
        ret = 123;
    else
        ret = 0;

    /* Destroy the service */
    java_destroy();

    /* Destroy the Java VM */
    if (JVM_destroy(ret) != true)
        return 7;

    return ret;
}

/*
 * freopen close the file first and then open the new file
 * that is not very good if we are try to trace the output
 * note the code assumes that the errors are configuration errors.
 */
static FILE *loc_freopen(char *outfile, char *mode, FILE * stream)
{
    FILE *ftest;

    ftest = fopen(outfile, mode);
    if (ftest == NULL) {
        fprintf(stderr, "Unable to redirect to %s\n", outfile);
        return stream;
    }
    fclose(ftest);
    return freopen(outfile, mode, stream);
}

#define LOGBUF_SIZE 1024

/* Read from file descriptors. Log to syslog. */
static int logger_child(int out_fd, int err_fd, char *procname)
{
    fd_set rfds;
    struct timeval tv;
    int retval, n;
    char buf[LOGBUF_SIZE];

    if (out_fd > err_fd) {
        n = out_fd + 1;
    } else {
        n = err_fd + 1;
    }

    openlog(procname, LOG_PID, LOG_DAEMON);

    while (1) {
        FD_ZERO(&rfds);
        FD_SET(out_fd, &rfds);
        FD_SET(err_fd, &rfds);
        tv.tv_sec = 60;
        tv.tv_usec = 0;
        retval = select(n, &rfds, NULL, NULL, &tv);
        if (retval == -1)
            syslog(LOG_ERR, "select: %s", strerror(errno));
        else if (retval) {
            if (FD_ISSET(out_fd, &rfds)) {
                ssize_t n = read(out_fd, buf, LOGBUF_SIZE-1);
                if (n < 0) {
                    syslog(LOG_ERR, "read: %s", strerror(errno));
                } else if (n > 0 && buf[0] != '\n') {
                    buf[n] = 0;
                    syslog(LOG_INFO, "%s", buf);
                }
            }
            if (FD_ISSET(err_fd, &rfds)) {
                ssize_t n = read(err_fd, buf, LOGBUF_SIZE-1);
                if (n < 0) {
                    syslog(LOG_ERR, "read: %s", strerror(errno));
                } else if (n > 0 && buf[0] != '\n') {
                    buf[n] = 0;
                    syslog(LOG_ERR, "%s", buf);
                }
            }
        }
    }
}

/**
 *  Redirect stdin, stdout, stderr.
 */
static void set_output(char *outfile, char *errfile, bool redirectstdin, char *procname)
{
    int out_pipe[2] = {0, 0}, err_pipe[2] = {0, 0}, fork_needed = 0;

    if (redirectstdin == true) {
        freopen("/dev/null", "r", stdin);
    }

    log_debug("redirecting stdout to %s and stderr to %s", outfile, errfile);

    /* make sure the debug goes out */
    if (log_debug_flag == true && strcmp(errfile, "/dev/null") == 0)
        return;

    /* Handle malicious case here */
    if (strcmp(outfile, "&2") == 0 && strcmp(errfile, "&1") == 0) {
        outfile = "/dev/null";
    }
    if (strcmp(outfile, "SYSLOG") == 0) {
        freopen("/dev/null", "a", stdout);
        /* Send stdout to syslog through a logger process */
        if (pipe(out_pipe) == -1) {
            log_error("cannot create stdout pipe: %s",
                      strerror(errno));
        } else {
            fork_needed = 1;
            log_stdout_syslog_flag = true;
        }
    } else if (strcmp(outfile, "&2") != 0) {
        loc_freopen(outfile, "a", stdout);
    }

    if (strcmp(errfile, "SYSLOG") == 0) {
        freopen("/dev/null", "a", stderr);
        /* Send stderr to syslog through a logger process */
        if (pipe(err_pipe) == -1) {
            log_error("cannot create stderr pipe: %s",
                      strerror(errno));
        } else {
            fork_needed = 1;
            log_stderr_syslog_flag = true;
        }
    } else if (strcmp(errfile, "&1") != 0) {
        loc_freopen(errfile, "a", stderr);
    }
    else {
        close(2);
        dup(1);
    }
    if (strcmp(outfile, "&2") == 0) {
        close(1);
        dup(2);
    }

    if (fork_needed) {
        pid_t pid = fork();
        if (pid == -1) {
            log_error("cannot create logger process: %s", strerror(errno));
        } else {
            if (pid) {
                logger_pid = pid;
                if (out_pipe[0] != 0) {
                    close(out_pipe[0]);
                    if (dup2(out_pipe[1], 1) == -1) {
                        log_error("cannot redirect stdout to pipe for syslog: %s",
                                  strerror(errno));
                    }
                }
                if (err_pipe[0] != 0) {
                    close(err_pipe[0]);
                    if (dup2(err_pipe[1], 2) == -1) {
                        log_error("cannot redirect stderr to pipe for syslog: %s",
                                  strerror(errno));
                    }
                }
            } else {
                exit(logger_child(out_pipe[0], err_pipe[0], procname));
            }<commit_msg>Fix stdout/stderr redirection.<commit_after>/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* @version $Id$ */
#include "jsvc.h"

#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <errno.h>
#ifdef OS_LINUX
#include <sys/prctl.h>
#include <sys/syscall.h>
#define _LINUX_FS_H
#include <linux/capability.h>
#ifdef HAVE_LIBCAP
#include <sys/capability.h>
#endif
#endif
#include <time.h>

#ifdef OS_CYGWIN
#include <sys/fcntl.h>
#define F_ULOCK 0               /* Unlock a previously locked region */
#define F_LOCK  1               /* Lock a region for exclusive use */
#endif

extern char **environ;

static mode_t envmask;          /* mask to create the files */

pid_t controlled = 0;           /* the child process pid */
pid_t logger_pid = 0;           /* the logger process pid */
static bool stopping = false;
static bool doreload = false;
static bool doreopen = false;
typedef void (*sighandler_t)(int);
static sighandler_t handler_int  = NULL;
static sighandler_t handler_usr1 = NULL;
static sighandler_t handler_hup  = NULL;
static sighandler_t handler_trm  = NULL;

static int run_controller(arg_data *args, home_data *data, uid_t uid,
                          gid_t gid);
static void set_output(char *outfile, char *errfile, bool redirectstdin,
                       char *procname);

#ifdef OS_CYGWIN
/*
 * File locking routine
 */
static int lockf(int fildes, int function, off_t size)
{
    struct flock buf;

    switch (function) {
    case F_LOCK:
        buf.l_type = F_WRLCK;
        break;
    case F_ULOCK:
        buf.l_type = F_UNLCK;
        break;
    default:
        return -1;
    }
    buf.l_whence = 0;
    buf.l_start = 0;
    buf.l_len = size;

    return fcntl(fildes, F_SETLK, &buf);
}

#endif

static void handler(int sig)
{
    switch (sig) {
        case SIGTERM:
            log_debug("Caught SIGTERM: Scheduling a shutdown");
            if (stopping == true) {
                log_error("Shutdown or reload already scheduled");
            }
            else {
                stopping = true;
            }
        break;
        case SIGINT:
            log_debug("Caught SIGINT: Scheduling a shutdown");
            if (stopping == true) {
                log_error("Shutdown or reload already scheduled");
            }
            else {
                stopping = true;
            }
        break;
        case SIGHUP:
            log_debug("Caught SIGHUP: Scheduling a reload");
            if (stopping == true) {
                log_error("Shutdown or reload already scheduled");
            }
            else {
                stopping = true;
                doreload = true;
            }
        break;
        case SIGUSR1:
             log_debug("Caught SIGUSR1: Reopening logs");
             doreopen = true;
        break;
        default:
            log_debug("Caught unknown signal %d", sig);
        break;
    }
}

/* user and group */
static int set_user_group(char *user, int uid, int gid)
{
    if (user != NULL) {
        if (setgid(gid) != 0) {
            log_error("Cannot set group id for user '%s'", user);
            return -1;
        }
        if (initgroups(user, gid) != 0) {
            if (getuid() != uid) {
                log_error("Cannot set supplement group list for user '%s'",
                          user);
                return -1;
            }
            else
                log_debug("Cannot set supplement group list for user '%s'",
                          user);
        }
        if (getuid() == uid) {
            log_debug("No need to change user to '%s'!", user);
            return 0;
        }
        if (setuid(uid) != 0) {
            log_error("Cannot set user id for user '%s'", user);
            return -1;
        }
        log_debug("user changed to '%s'", user);
    }
    return 0;
}

/* Set linux capability, user and group */
#ifdef OS_LINUX
#ifdef HAVE_LIBCAP
static cap_value_t caps_std[] = {
    CAP_NET_BIND_SERVICE,
    CAP_SETUID,
    CAP_SETGID,
    CAP_DAC_READ_SEARCH,
    CAP_DAC_OVERRIDE
};

static cap_value_t caps_min[] = {
    CAP_NET_BIND_SERVICE,
    CAP_DAC_READ_SEARCH,
    CAP_DAC_OVERRIDE
};

#define CAPS     1
#define CAPSMIN  2

static int set_caps(int cap_type)
{
    cap_t c;
    int ncap;
    cap_value_t *caps;

    if (cap_type == CAPS) {
        ncap = sizeof(caps_std)/sizeof(cap_value_t);
        caps = caps_std;
    }
    else {
        ncap = sizeof(caps_min)/sizeof(cap_value_t);
        caps = caps_min;
    }
    c = cap_init();
    cap_clear(c);
    cap_set_flag(c, CAP_EFFECTIVE,   ncap, caps, CAP_SET);
    cap_set_flag(c, CAP_INHERITABLE, ncap, caps, CAP_SET);
    cap_set_flag(c, CAP_PERMITTED,   ncap, caps, CAP_SET);
    if (cap_set_proc(c) != 0) {
        log_error("failed setting %s capabilities.",
                  cap_type == CAPS ? "default" : "min");
        return -1;
    }
    cap_free(c);
    if (cap_type == CAPS)
        log_debug("increased capability set.");
    else
        log_debug("decreased capability set to min required.");
    return 0;
}

#else /* !HAVE_LIBCAP */
/* CAPSALL is to allow to read/write at any location */
#define CAPSALL (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_SETUID) +             \
                (1 << CAP_SETGID) +             \
                (1 << CAP_DAC_READ_SEARCH) +    \
                (1 << CAP_DAC_OVERRIDE)

#define CAPSMAX (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_DAC_READ_SEARCH) +    \
                (1 << CAP_DAC_OVERRIDE)

/* That a more reasonable configuration */
#define CAPS    (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_DAC_READ_SEARCH) +    \
                (1 << CAP_SETUID) +             \
                (1 << CAP_SETGID)

/* probably the only one Java could use */
#define CAPSMIN (1 << CAP_NET_BIND_SERVICE) +   \
                (1 << CAP_DAC_READ_SEARCH)

#define LEGACY_CAP_VERSION  0x19980330
static int set_caps(int caps)
{
    struct __user_cap_header_struct caphead;
    struct __user_cap_data_struct   cap;

    memset(&caphead, 0, sizeof caphead);
    caphead.version = LEGACY_CAP_VERSION;
    caphead.pid = 0;
    memset(&cap, 0, sizeof cap);
    cap.effective = caps;
    cap.permitted = caps;
    cap.inheritable = caps;
    if (syscall(__NR_capset, &caphead, &cap) < 0) {
        log_error("set_caps: failed to set capabilities");
        log_error("check that your kernel supports capabilities");
        return -1;
    }
    return 0;
}
#endif

static int linuxset_user_group(char *user, int uid, int gid)
{
    int caps_set = 0;
    /* set capabilities enough for binding port 80 setuid/getuid */
    if (getuid() == 0) {
        if (set_caps(CAPS) != 0) {
            if (getuid() != uid) {
                log_error("set_caps(CAPS) failed");
                return -1;
            }
            log_debug("set_caps(CAPS) failed");
        }
        /* make sure they are kept after setuid */
        if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
            log_error("prctl failed in linuxset_user_group");
            return -1;
        }
        caps_set = 1;
    }

    /* set setuid/getuid */
    if (set_user_group(user, uid, gid) != 0) {
        log_error("set_user_group failed in linuxset_user_group");
        return -1;
    }

    if (caps_set) {
        /* set capability to binding port 80 read conf */
        if (set_caps(CAPSMIN) != 0) {
            if (getuid() != uid) {
                log_error("set_caps(CAPSMIN) failed");
                return -1;
            }
            log_debug("set_caps(CAPSMIN) failed");
        }
    }

    return 0;
}
#endif


static bool checkuser(char *user, uid_t * uid, gid_t * gid)
{
    struct passwd *pwds = NULL;
    int status = 0;
    pid_t pid  = 0;

    /* Do we actually _have_ to switch user? */
    if (user == NULL)
        return true;

    pwds = getpwnam(user);
    if (pwds == NULL) {
        log_error("Invalid user name '%s' specified", user);
        return false;
    }

    *uid = pwds->pw_uid;
    *gid = pwds->pw_gid;

    /* Validate the user name in another process */
    pid = fork();
    if (pid == -1) {
        log_error("Cannot validate user name");
        return false;
    }

    /* If we're in the child process, let's validate */
    if (pid == 0) {
        if (set_user_group(user, *uid, *gid) != 0)
            exit(1);
        /* If we got here we switched user/group */
        exit(0);
    }

    while (waitpid(pid, &status, 0) != pid) {
        /* Just wait */
    }

    /* The child must have exited cleanly */
    if (WIFEXITED(status)) {
        status = WEXITSTATUS(status);

        /* If the child got out with 0 the user is ok */
        if (status == 0) {
            log_debug("User '%s' validated", user);
            return true;
        }
    }

    log_error("Error validating user '%s'", user);
    return false;
}

#ifdef OS_CYGWIN
static void cygwincontroller(void)
{
    raise(SIGTERM);
}
#endif
static void controller(int sig)
{
    switch (sig) {
        case SIGTERM:
        case SIGINT:
        case SIGHUP:
        case SIGUSR1:
            log_debug("Forwarding signal %d to process %d", sig, controlled);
            kill(controlled, sig);
            signal(sig, controller);
        break;
        default:
            log_debug("Caught unknown signal %d", sig);
        break;
    }
}

/*
 * Return the address of the current signal handler and set the new one.
 */
static sighandler_t signal_set(int sig, sighandler_t newHandler)
{
    sighandler_t hand;

    hand = signal(sig, newHandler);
#ifdef SIG_ERR
    if (hand == SIG_ERR)
        hand = NULL;
#endif
    if (hand == handler || hand == controller)
        hand = NULL;
    return hand;
}

/*
 * Check pid and if still running
 */

static int check_pid(arg_data *args)
{
    int fd;
    FILE *pidf;
    char buff[80];
    pid_t pidn = getpid();
    int i, pid;

    fd = open(args->pidf, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        log_error("Cannot open PID file %s, PID is %d", args->pidf, pidn);
        return -1;
    }
    else {
        lockf(fd, F_LOCK, 0);
        i = read(fd, buff, sizeof(buff));
        if (i > 0) {
            buff[i] = '\0';
            pid = atoi(buff);
            if (kill(pid, 0) == 0) {
                log_error("Still running according to PID file %s, PID is %d",
                          args->pidf, pid);
                lockf(fd, F_ULOCK, 0);
                close(fd);
                return 122;
            }
        }

        /* skip writing the pid file if version or check */
        if (args->vers != true && args->chck != true) {
            lseek(fd, SEEK_SET, 0);
            pidf = fdopen(fd, "r+");
            fprintf(pidf, "%d\n", (int)getpid());
            fflush(pidf);
            lockf(fd, F_ULOCK, 0);
            fclose(pidf);
            close(fd);
        }
        else {
            lockf(fd, F_ULOCK, 0);
            close(fd);
        }
    }
    return 0;
}

/*
 * read the pid from the pidfile
 */
static int get_pidf(arg_data *args)
{
    int fd;
    int i;
    char buff[80];

    fd = open(args->pidf, O_RDONLY, 0);
    log_debug("get_pidf: %d in %s", fd, args->pidf);
    if (fd < 0) {
        /* something has gone wrong the JVM has stopped */
        return -1;
    }
    lockf(fd, F_LOCK, 0);
    i = read(fd, buff, sizeof(buff));
    lockf(fd, F_ULOCK, 0);
    close(fd);
    if (i > 0) {
        buff[i] = '\0';
        i = atoi(buff);
        log_debug("get_pidf: pid %d", i);
        if (kill(i, 0) == 0)
            return i;
    }
    return -1;
}

/*
 * Check temporatory file created by controller
 * /tmp/pid.jsvc_up
 * Notes:
 * we fork several times
 * 1 - to be a daemon before the setsid(), the child is the controler process.
 * 2 - to start the JVM in the child process. (whose pid is stored in pidfile).
 */
static int check_tmp_file(arg_data *args)
{
    int pid;
    char buff[80];
    int fd;

    pid = get_pidf(args);
    if (pid < 0)
        return -1;
    sprintf(buff, "/tmp/%d.jsvc_up", pid);
    log_debug("check_tmp_file: %s", buff);
    fd = open(buff, O_RDONLY);
    if (fd == -1)
        return -1;
    close(fd);
    return 0;
}

static void create_tmp_file(arg_data *args)
{
    char buff[80];
    int fd;

    sprintf(buff, "/tmp/%d.jsvc_up", (int)getpid());
    log_debug("create_tmp_file: %s", buff);
    fd = open(buff, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd != -1)
        close(fd);
}

static void remove_tmp_file(arg_data *args)
{
    char buff[80];

    sprintf(buff, "/tmp/%d.jsvc_up", (int)getpid());
    log_debug("remove_tmp_file: %s", buff);
    unlink(buff);
}

/*
 * wait until jsvc create the I am ready file
 * pid is the controller and args->pidf the JVM itself.
 */
static int wait_child(arg_data *args, int pid)
{
    int count = 10;
    bool havejvm = false;
    int fd;
    char buff[80];
    int i, status, waittime;

    log_debug("wait_child %d", pid);
    waittime = args->wait / 10;
    if (waittime > 10) {
        count = waittime;
        waittime = 10;
    }
    while (count > 0) {
        sleep(1);
        /* check if the controler is still running */
        if (waitpid(pid, &status, WNOHANG) == pid) {
            if (WIFEXITED(status))
                return (WEXITSTATUS(status));
            else
                return 1;
        }

        /* check if the pid file process exists */
        fd = open(args->pidf, O_RDONLY);
        if (fd < 0 && havejvm) {
            /* something has gone wrong the JVM has stopped */
            return 1;
        }
        lockf(fd, F_LOCK, 0);
        i = read(fd, buff, sizeof(buff));
        lockf(fd, F_ULOCK, 0);
        close(fd);
        if (i > 0) {
            buff[i] = '\0';
            i = atoi(buff);
            if (kill(i, 0) == 0) {
                /* the JVM process has started */
                havejvm = true;
                if (check_tmp_file(args) == 0) {
                    /* the JVM is started */
                    if (waitpid(pid, &status, WNOHANG) == pid) {
                        if (WIFEXITED(status))
                            return (WEXITSTATUS(status));
                        else
                            return 1;
                    }
                    return 0; /* ready JVM started */
                }
            }
        }
        sleep(waittime);
        count--;
    }
    /* It takes more than the wait time to start,
     * something must be wrong
     */
    return 1;
}

/*
 * stop the running jsvc
 */
static int stop_child(arg_data *args)
{
    int pid = get_pidf(args);
    int count = 10;

    if (pid > 0) {
        /* kill the process and wait until the pidfile has been
         * removed by the controler
         */
        kill(pid, SIGTERM);
        while (count > 0) {
            sleep(6);
            pid = get_pidf(args);
            if (pid <= 0) {
                /* JVM has stopped */
                return 0;
            }
            count--;
        }
    }
    return -1;
}

/*
 * child process logic.
 */

static int child(arg_data *args, home_data *data, uid_t uid, gid_t gid)
{
    int ret = 0;

    /* check the pid file */
    ret = check_pid(args);
    if (args->vers != true && args->chck != true) {
        if (ret == 122)
            return ret;
        if (ret < 0)
            return ret;
    }

    /* create a new process group to prevent kill 0 killing the monitor process */
#if defined(OS_FREEBSD) || defined(OS_DARWIN)
    setpgid(0, 0);
#else
    setpgrp();
#endif

#ifdef OS_LINUX
    /* setuid()/setgid() only apply the current thread so we must do it now */
    if (linuxset_user_group(args->user, uid, gid) != 0)
        return 4;
#endif
    /* Initialize the Java VM */
    if (java_init(args, data) != true) {
        log_debug("java_init failed");
        return 1;
    }
    else
        log_debug("java_init done");

    /* Check wether we need to dump the VM version */
    if (args->vers == true) {
        if (java_version() != true) {
            return -1;
        }
        else
            return 0;
    }
    /* Check wether we need to dump the VM version */
    else if (args->vershow == true) {
        if (java_version() != true) {
            return 7;
        }
    }

    /* Do we have to do a "check-only" initialization? */
    if (args->chck == true) {
        if (java_check(args) != true)
            return 2;
        printf("Service \"%s\" checked successfully\n", args->clas);
        return 0;
    }

    /* Load the service */
    if (java_load(args) != true) {
        log_debug("java_load failed");
        return 3;
    }
    else
        log_debug("java_load done");

    /* Downgrade user */
#ifdef OS_LINUX
    if (set_caps(0) != 0) {
        log_debug("set_caps (0) failed");
        return 4;
    }
#else
    if (set_user_group(args->user, uid, gid) != 0)
        return 4;
#endif

    /* Start the service */
    umask(envmask);
    if (java_start() != true) {
        log_debug("java_start failed");
        return 5;
    }
    else
        log_debug("java_start done");

    /* Install signal handlers */
    handler_hup = signal_set(SIGHUP, handler);
    handler_usr1 = signal_set(SIGUSR1, handler);
    handler_trm = signal_set(SIGTERM, handler);
    handler_int = signal_set(SIGINT, handler);
    controlled = getpid();

    log_debug("Waiting for a signal to be delivered");
    create_tmp_file(args);
    while (!stopping) {
#if defined(OSD_POSIX) || defined(HAVE_KAFFEVM)
        java_sleep(60);
        /* pause(); */
#else
        /* pause() is not threadsafe */
        sleep(60);
#endif
        if(doreopen) {
            doreopen = false;
            set_output(args->outfile, args->errfile, args->redirectstdin, args->procname);
        }
    }
    remove_tmp_file(args);
    log_debug("Shutdown or reload requested: exiting");

    /* Stop the service */
    if (java_stop() != true)
        return 6;

    if (doreload == true)
        ret = 123;
    else
        ret = 0;

    /* Destroy the service */
    java_destroy();

    /* Destroy the Java VM */
    if (JVM_destroy(ret) != true)
        return 7;

    return ret;
}

/*
 * freopen close the file first and then open the new file
 * that is not very good if we are try to trace the output
 * note the code assumes that the errors are configuration errors.
 */
static FILE *loc_freopen(char *outfile, char *mode, FILE * stream)
{
    FILE *ftest;

    ftest = fopen(outfile, mode);
    if (ftest == NULL) {
        fprintf(stderr, "Unable to redirect to %s\n", outfile);
        return stream;
    }
    fclose(ftest);
    return freopen(outfile, mode, stream);
}

#define LOGBUF_SIZE 1024

/* Read from file descriptors. Log to syslog. */
static int logger_child(int out_fd, int err_fd, char *procname)
{
    fd_set rfds;
    struct timeval tv;
    int retval, n;
    char buf[LOGBUF_SIZE];

    if (out_fd > err_fd) {
        n = out_fd + 1;
    } else {
        n = err_fd + 1;
    }

    openlog(procname, LOG_PID, LOG_DAEMON);

    while (1) {
        FD_ZERO(&rfds);
        FD_SET(out_fd, &rfds);
        FD_SET(err_fd, &rfds);
        tv.tv_sec = 60;
        tv.tv_usec = 0;
        retval = select(n, &rfds, NULL, NULL, &tv);
        if (retval == -1)
            syslog(LOG_ERR, "select: %s", strerror(errno));
        else if (retval) {
            if (FD_ISSET(out_fd, &rfds)) {
                ssize_t n = read(out_fd, buf, LOGBUF_SIZE-1);
                if (n < 0) {
                    syslog(LOG_ERR, "read: %s", strerror(errno));
                } else if (n > 0 && buf[0] != '\n') {
                    buf[n] = 0;
                    syslog(LOG_INFO, "%s", buf);
                }
            }
            if (FD_ISSET(err_fd, &rfds)) {
                ssize_t n = read(err_fd, buf, LOGBUF_SIZE-1);
                if (n < 0) {
                    syslog(LOG_ERR, "read: %s", strerror(errno));
                } else if (n > 0 && buf[0] != '\n') {
                    buf[n] = 0;
                    syslog(LOG_ERR, "%s", buf);
                }
            }
        }
    }
}

/**
 *  Redirect stdin, stdout, stderr.
 */
static void set_output(char *outfile, char *errfile, bool redirectstdin, char *procname)
{
    int out_pipe[2] = {0, 0}, err_pipe[2] = {0, 0}, fork_needed = 0;

    if (redirectstdin == true) {
        freopen("/dev/null", "r", stdin);
    }

    log_debug("redirecting stdout to %s and stderr to %s", outfile, errfile);

    /* make sure the debug goes out */
    if (log_debug_flag == true && strcmp(errfile, "/dev/null") == 0)
        return;
    if (strcmp(outfile, "&1") == 0 && strcmp(errfile, "&2") == 0)
        return;
    if (strcmp(outfile, "SYSLOG") == 0) {
        freopen("/dev/null", "a", stdout);
        /* Send stdout to syslog through a logger process */
        if (pipe(out_pipe) == -1) {
            log_error("cannot create stdout pipe: %s",
                      strerror(errno));
        }
        else {
            fork_needed = 1;
            log_stdout_syslog_flag = true;
        }
    }
    else if (strcmp(outfile, "&2")) {
        if (strcmp(outfile, "&1")) {
            /* Redirect stdout to a file */
            loc_freopen(outfile, "a", stdout);
        }
    }

    if (strcmp(errfile, "SYSLOG") == 0) {
        freopen("/dev/null", "a", stderr);
        /* Send stderr to syslog through a logger process */
        if (pipe(err_pipe) == -1) {
            log_error("cannot create stderr pipe: %s",
                      strerror(errno));
        }
        else {
            fork_needed = 1;
            log_stderr_syslog_flag = true;
        }
    }
    else if (strcmp(errfile, "&1")) {
        if (strcmp(errfile, "&2")) {
            /* Redirect stderr to a file */
            loc_freopen(errfile, "a", stderr);
        }
    }
    if (strcmp(errfile, "&1") == 0 && strcmp(outfile, "&1")) {
        /*
         * -errfile &1 -outfile foo
         * Redirect stderr to stdout
         */
        close(2);
        dup2(1, 2);
    }
    if (strcmp(outfile, "&2") == 0 && strcmp(errfile, "&2")) {
        /*
         * -outfile &2 -errfile foo
         * Redirect stdout to stderr
         */
        close(1);
        dup2(2, 1);
    }

    if (fork_needed) {
        pid_t pid = fork();
        if (pid == -1) {
            log_error("cannot create logger process: %s", strerror(errno));
        } else {
            if (pid) {
                logger_pid = pid;
                if (out_pipe[0] != 0) {
                    close(out_pipe[0]);
                    if (dup2(out_pipe[1], 1) == -1) {
                        log_error("cannot redirect stdout to pipe for syslog: %s",
                                  strerror(errno));
                    }
                }
                if (err_pipe[0] != 0) {
                    close(err_pipe[0]);
                    if (dup2(err_pipe[1], 2) == -1) {
                        log_error("cannot redirect stderr to pipe for syslog: %s",
                                  strerror(errno));
                    }
                }
            } else {
                exit(logger_child(out_pipe[0], err_pipe[0], procname));
            }
{'fraction_non_alphanumeric': 0.124520067977257, 'fraction_numerical': 0.011329542831966095, 'mean_word_length': 1.7685873605947955, 'pattern_counts': {'":': 0, '<': 115, '<?xml version=': 0, '>': 107, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2345218', 'n_tokens_mistral': 16273, 'n_tokens_neox': 15537, 'n_words': 4394}