/*
 * FCRON - periodic command scheduler
 *
 *  Copyright 2000-2024 Thibault Godouet <fcron@free.fr>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  The GNU General Public License can also be found in the file
 *  `LICENSE' that comes with the fcron source distribution.
 */


/* This file contains all fcron's code (server) to handle communication with fcrondyn */

#include "fcron.h"
#include "fcrondyn_svr.h"
#include "select.h"
#include "getloadavg.h"
#include "database.h"
#include "fcronconf.h"


void remove_connection(struct fcrondyn_cl **client,
                       struct fcrondyn_cl *prev_client, select_instance * si);
void exe_cmd(struct fcrondyn_cl *client);
#ifdef SO_PEERCRED              /* linux */
void auth_client_so_peercred(struct fcrondyn_cl *client);
#elif defined(HAVE_GETPEERUCRED) || defined(HAVE_GETPEEREID)    /* resp. solaris 10 and Free/OpenBSD) */
void auth_client_getpeer(struct fcrondyn_cl *client);
#endif
void auth_client_password(struct fcrondyn_cl *client);
void cmd_ls(struct fcrondyn_cl *client, long int *cmd, int fd, int is_root);
void print_fields(int fd, unsigned char *details);
void print_line(int fd, struct cl_t *line, unsigned char *details, pid_t pid,
                int index, time_t until);
void cmd_on_exeq(struct fcrondyn_cl *client, long int *cmd, int fd,
                 int is_root);
void cmd_renice(struct fcrondyn_cl *client, long int *cmd, int fd, exe_t * e,
                int is_root);
void cmd_send_signal(struct fcrondyn_cl *client, long int *cmd, int fd,
                     exe_t * e);
void cmd_run(struct fcrondyn_cl *client, long int *cmd, int fd, int is_root);

fcrondyn_cl *fcrondyn_cl_base;  /* list of connected fcrondyn clients */
int fcrondyn_cl_num = 0;        /* number of fcrondyn clients currently connected */
int listen_fd = -1;             /* fd which catches incoming connection */
int auth_fail = 0;              /* number of auth failure */
time_t auth_nofail_since = 0;   /* we refuse auth since x due to too many failures */
static char truncated[] = " (truncated)\n";

/* some error messages ... */
char err_no_err_str[] = "Command successfully completed.\n";
char err_unknown_str[] =
    "Fcron has encountered an error : command not completed.\n";
char err_cmd_unknown_str[] = "Unknown command.\n";
char err_job_nfound_str[] = "No corresponding job found.\n";
char err_rjob_nfound_str[] =
    "No corresponding running job found.\n (The job may have "
    "just finished its execution.)\n";
char err_invalid_user_str[] = "Invalid user : unable to find a passwd entry.\n";
char err_invalid_args_str[] = "Invalid arguments.\n";
char err_job_nallowed_str[] = "You are not allowed to see/change this line.\n";
char err_all_nallowed_str[] = "You are not allowed to list all jobs.\n";
char err_others_nallowed_str[] =
    "You are not allowed to list other users' jobs.\n";

/* Send an error message to fcrondyn */
#define Send_err_msg(FD, MSG) \
          send(FD, MSG, sizeof(MSG), 0);

/* to tell fcrondyn there's no more data to wait */
#define Tell_no_more_data(FD) \
	    send(FD, END_STR, sizeof(END_STR), 0);

/* Send an error message to fcrondyn and return */
#define Send_err_msg_end(FD, MSG) \
        { \
          send(FD, MSG, sizeof(MSG), 0); \
          Tell_no_more_data(FD); \
        }

/* which bit corresponds to which field ? */
#define FIELD_USER 0
#define FIELD_RQ 1
#define FIELD_PID 2
#define FIELD_SCHEDULE 3
#define FIELD_LAVG 4
#define FIELD_INDEX 5
#define FIELD_OPTIONS 6

/* the number of char we need (8 bits (i.e. fields) per char) */
#define FIELD_NUM_SIZE 1


void
fcrondyn_socket_init(select_instance * si)
    /* do everything needed to get a working listening socket */
{
    struct sockaddr_un addr;
    int len = 0;
    int sun_len = 0;

    if ((listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
        error_e("Could not create socket : fcrondyn won't work");
        return;
    }

    addr.sun_family = AF_UNIX;
    len = strlen(fifofile);
    if (len > sizeof(addr.sun_path) - 1) {
        error("Error : fifo file path too long (max is %d)",
              sizeof(addr.sun_path) - 1);
        goto err;
    }
    strncpy(addr.sun_path, fifofile, sizeof(addr.sun_path));
    addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
    sun_len = (addr.sun_path - (char *)&addr) + len;
#if HAVE_SA_LEN
    addr.sun_len = sun_len;
#endif

    unlink(fifofile);
    if (bind(listen_fd, (struct sockaddr *)&addr, sun_len) != 0) {
        error_e("Cannot bind socket to '%s'", fifofile);
        goto err;
    }

    if (listen(listen_fd, MAX_CONNECTION) != 0) {
        error_e("Cannot set socket in listen mode");
        goto err;
    }

    /* */
    /* The exec bit is not necessary and ignored on all systems but AIX, where it is
     * needed to allow fcrondyn to open the file */
    if (chmod
        (fifofile,
         S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH |
         S_IWOTH | S_IXOTH) != 0)
        error_e("Cannot chmod() socket file");
    /* */

    fcntl(listen_fd, F_SETFD, 1);
    /* set listen_fd to O_NONBLOCK : we do not want fcron to be stopped on error, etc */
    if (fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) | O_NONBLOCK) == -1) {
        error_e
            ("Could not set listen_fd attribute O_NONBLOCK : no fcrondyn support");
        goto err;
    }

    /* no error */
    select_add_read(si, listen_fd);

    debug("Socket initialized : listen_fd : %d", listen_fd);
    return;

 err:
    fcrondyn_socket_close(si);

}

#if defined(HAVE_GETPEERUCRED)
void
auth_client_getpeer(struct fcrondyn_cl *client)
    /* check client identity by reading its credentials from the socket
     * using getpeerucred() (Solaris 10 onward).
     * Sets client->fcl_user on success, don't do anything on failure
     * so that the client stays unauthenticated */
{
    struct passwd *p_entry = NULL;
    ucred_t *ucred = NULL;
    uid_t uid = -1;

    if (getpeerucred(client->fcl_sock_fd, &ucred) < 0) {
        error_e("Could not get client credentials using getpeerucred()");
        return;
    }
    uid = ucred_getruid(ucred);
    if (uid == -1) {
        error_e("Could not get client uid from ucred_t");
        return;
    }
    p_entry = getpwuid(uid);
    if (p_entry == NULL) {
        error_e("Could not find password entry for uid %d", uid);
        return;
    }

    /* Successfully identified user: */
    client->fcl_user = strdup2(p_entry->pw_name);

    explain("Client's pid=%d, uid=%d, username=%s",
            ucred_getpid(ucred), uid, client->fcl_user);

}
#endif                          /* HAVE_GETPEERUCRED */

#if defined(HAVE_GETPEEREID)
/*
 * WARNING: UNTESTED CODE !!!
 */
void
auth_client_getpeer(struct fcrondyn_cl *client)
    /* check client identity by reading its credentials from the socket
     * using getpeerucred() (Solaris 10 onward) or getpeereid(open/freeBSD).
     * Sets client->fcl_user on success, don't do anything on failure
     * so that the client stays unauthenticated */
{
    struct passwd *p_entry = NULL;
    uid_t euid = -1;
    gid_t egid = -1;

    if (getpeereid(client->fcl_sock_fd, &euid, &egid) < 0) {
        error_e("Could not get client credentials using getpeereid()");
        return;
    }
    p_entry = getpwuid(euid);
    if (p_entry == NULL) {
        error_e("Could not find password entry for uid %d", euid);
        return;
    }

    /* Successfully identified user: */
    client->fcl_user = strdup2(p_entry->pw_name);

    explain("Client's uid=%d, gid=%d username=%s", euid, egid,
            client->fcl_user);

}
#endif                          /* HAVE_GETPEEREID */



#ifdef SO_PEERCRED
void
auth_client_so_peercred(struct fcrondyn_cl *client)
    /* check client identity by reading its credentials from the socket
     * using getsockopt(SO_PEERCRED) (Linux)
     * Sets client->fcl_user on success, don't do anything on failure
     * so that the client stays unauthenticated */
{
    const int true_val = 1;
    /* There is no ucred.h (or equivalent) on linux to define struct ucred (!!)
     * so we do it here */
#if ! ( defined(HAVE_CRED_H) && defined(HAVE_UCRED_H) \
		&& defined(HAVE_SYS_CRED_H) && defined(HAVE_SYS_UCRED_H) )
    struct ucred {
        pid_t pid;
        uid_t uid;
        gid_t gid;
    };
#endif                          /* struct ucred not defined */
    struct ucred cred;
    socklen_t cred_size = sizeof(cred);
    struct passwd *p_entry = NULL;

    setsockopt(client->fcl_sock_fd, SOL_SOCKET, SO_PASSCRED, &true_val,
               sizeof(true_val));
    if (getsockopt
        (client->fcl_sock_fd, SOL_SOCKET, SO_PEERCRED, &cred,
         &cred_size) != 0) {
        error_e
            ("Could not get client credentials using getsockopt(SO_PEERCRED)");
        return;
    }

    p_entry = getpwuid(cred.uid);
    if (p_entry == NULL) {
        error_e("Could not find password entry for uid %d", cred.uid);
        return;
    }

    /* Successfully identified user: */
    client->fcl_user = strdup2(p_entry->pw_name);

    explain("Client's pid=%d, uid=%d, gid=%d username=%s", cred.pid, cred.uid,
            cred.gid, client->fcl_user);

}
#endif                          /* SO_PEERCRED */

void
auth_client_password(struct fcrondyn_cl *client)
    /* check client identity by asking him to input his password */
{
    char *pass_cry = NULL;
    char *pass_sys = NULL;
    char *pass_str = NULL;

#ifdef HAVE_GETSPNAM
    struct spwd *pass_sp = NULL;
    if ((pass_sp = getspnam((char *)client->fcl_cmd)) == NULL) {
        error_e("could not getspnam %s", (char *)client->fcl_cmd);
        send(client->fcl_sock_fd, "0", sizeof("0"), 0);
        return;
    }
    pass_sys = pass_sp->sp_pwdp;
#else
    struct passwd *pass = NULL;
    errno = 0;
    if ((pass = getpwnam((char *)client->fcl_cmd)) == NULL) {
        error_e("could not getpwnam %s", (char *)client->fcl_cmd);
        send(client->fcl_sock_fd, "0", sizeof("0"), 0);
        return;
    }
    pass_sys = pass->pw_passwd;
#endif

    /* */
    debug("auth_client_password() : socket : %d", client->fcl_sock_fd);
    /* */

    /* we need to limit auth failures : otherwise fcron may be used to "read"
     * shadow password !!! (or to crack it using a test-all-possible-password attack) */
    if (auth_fail > 0 && auth_nofail_since + AUTH_WAIT <= now)
        /* no auth time exceeded : set counter to 0 */
        auth_fail = 0;
    if (auth_fail >= MAX_AUTH_FAIL) {
        error("Too many authentication failures : try to connect later.");
        send(client->fcl_sock_fd, "0", sizeof("0"), 0);
        auth_fail = auth_nofail_since = 0;
        return;
    }

    /* the password is stored after the user name */
    pass_str = &((char *)client->fcl_cmd)[strlen((char *)client->fcl_cmd) + 1];
    if ((pass_cry = crypt(pass_str, pass_sys)) == NULL) {
        error_e("could not crypt()");
        send(client->fcl_sock_fd, "0", sizeof("0"), 0);
        Overwrite(pass_str);
        return;
    }

/*      debug("pass_sp->sp_pwdp : %s", pass_sp->sp_pwdp); */
/*      debug("pass_cry : %s", pass_cry); */
    if (strcmp(pass_cry, pass_sys) == 0) {
        client->fcl_user = strdup2((char *)client->fcl_cmd);
        send(client->fcl_sock_fd, "1", sizeof("1"), 0);
    }
    else {
        auth_fail++;
        auth_nofail_since = now;
        error("Invalid passwd for %s from socket %d",
              (char *)client->fcl_cmd, client->fcl_sock_fd);
        send(client->fcl_sock_fd, "0", sizeof("0"), 0);
    }

    Overwrite(pass_str);
}


#define Test_add_field(FIELD_NB, FIELD_STR) \
    if ( (bit_test(details, FIELD_NB)) ) { \
        strncat(fields, FIELD_STR, sizeof(fields) - len -1); \
        len += (sizeof(FIELD_STR)-1); \
    }
#define Add_field(FIELD_STR) \
    strncat(fields, FIELD_STR, sizeof(fields) - len - 1); \
    len += (sizeof(FIELD_STR)-1);

void
print_fields(int fd, unsigned char *details)
    /* print a line describing the field types used in print_line() */
{
    char fields[TERM_LEN];
    char field_id[] = "ID   ";
    char field_user[] = "|USER     ";
    char field_rq[] = "|R&Q ";
    char field_options[] = "|OPTIONS  ";
    char field_schedule[] = "|SCHEDULE           ";
    char field_until[] = "|LAVG 1,5,15 UNTIL       STRICT";
    char field_pid[] = "|PID    ";
    char field_index[] = "|INDEX";
    char field_cmd[] = "|CMD";
    char field_endline[] = "\n";
    int len = 0;

    fields[0] = '\0';

    Add_field(field_id);
    Test_add_field(FIELD_USER, field_user);
    Test_add_field(FIELD_PID, field_pid);
    Test_add_field(FIELD_INDEX, field_index);
    Test_add_field(FIELD_RQ, field_rq);
    Test_add_field(FIELD_OPTIONS, field_options);
    Test_add_field(FIELD_LAVG, field_until);
    Test_add_field(FIELD_SCHEDULE, field_schedule);
    Add_field(field_cmd);
    Add_field(field_endline);

    /* Extra safety (which should be redundant as strncat (used in Add_field
       and Test_add_field) always null-terminate the string: */
    fields[sizeof(fields) - 1] = '\0';

    /* add +1 to include the final end-of-string "\0" */
    if (send(fd, fields, (len+1 < sizeof(fields)) ? len+1 : sizeof(fields), 0) < 0)
        error_e("error in send()");

}


void
print_line(int fd, struct cl_t *line, unsigned char *details, pid_t pid,
           int index, time_t until)
    /* print some basic fields of a line, and some more if details == 1 */
{
    char buf[TERM_LEN];
    int len = 0;
    struct tm *ftime;


    len = snprintf(buf, sizeof(buf), "%-5ld", line->cl_id);
    if (bit_test(details, FIELD_USER))
        len +=
            snprintf(buf + len, sizeof(buf) - len, "|%-9s",
                     line->cl_file->cf_user);
    if (bit_test(details, FIELD_PID))
        len += snprintf(buf + len, sizeof(buf) - len, "|%-7d", (int)pid);
    if (bit_test(details, FIELD_INDEX))
        len += snprintf(buf + len, sizeof(buf) - len, "|%-5d", index);
    if (bit_test(details, FIELD_RQ))
        len += snprintf(buf + len, sizeof(buf) - len, "|%-4d", line->cl_numexe);
    if (bit_test(details, FIELD_OPTIONS)) {
        char opt[9];
        int i = 0;
        opt[0] = '\0';
        if (is_lavg(line->cl_option))
            i += snprintf(opt + i, sizeof(opt) - i, "L%.*s",
                          (is_lavg_sev(line->cl_option)) ? 0 : 1, "O");
        if (is_serial(line->cl_option))
            i += snprintf(opt + i, sizeof(opt) - i, "%.*sS%.*s", i, ",",
                          (is_serial_sev(line->cl_option)) ? 0 : 1, "O");
        if (is_exe_sev(line->cl_option))
            i += snprintf(opt + i, sizeof(opt) - i, "%.*sES", i, ",");

        len += snprintf(buf + len, sizeof(buf) - len, "|%-9s", opt);
    }
    if (bit_test(details, FIELD_LAVG)) {
        len += snprintf(buf + len, sizeof(buf) - len, "|%.1f,%.1f,%.1f",
                        ((double)((line->cl_lavg)[0])) / 10,
                        ((double)((line->cl_lavg)[1])) / 10,
                        ((double)((line->cl_lavg)[2])) / 10);
        if (until > 0) {
            ftime = localtime(&until);
            len +=
                snprintf(buf + len, sizeof(buf) - len,
                         " %04d-%02d-%02d %02d:%02d:%02d %s",
                         (ftime->tm_year + 1900), (ftime->tm_mon + 1),
                         ftime->tm_mday, ftime->tm_hour, ftime->tm_min,
                         ftime->tm_sec,
                         (is_strict(line->cl_option)) ? "Y" : "N");
        }
        else
            len +=
                snprintf(buf + len, sizeof(buf) - len, " %18s",
                         " (no until set) ");
    }
    if (bit_test(details, FIELD_SCHEDULE)) {
        ftime = localtime(&(line->cl_nextexe));
        len +=
            snprintf(buf + len, sizeof(buf) - len, "|%04d-%02d-%02d %02d:%02d:%02d",
                     (ftime->tm_year + 1900), (ftime->tm_mon + 1),
                     ftime->tm_mday, ftime->tm_hour, ftime->tm_min, ftime->tm_sec);
    }
    len += snprintf(buf + len, sizeof(buf) - len, "|%s\n", line->cl_shell);

    /* snprintf() returns the length of what would have been written (excluding terminating null byte)
      if not limited by maxlen */
    if (len+1 > sizeof(buf)) {
        /* the shell command string was too long and was truncated */
        strcpy(buf + sizeof(buf) - sizeof(truncated), truncated);
    }

    /* as extra safety to make sure the string is always null-terminated
      (even though snprintf man page suggests it does it already) */
    buf[sizeof(buf) - 1] = '\0';

    /* add +1 to include the final end-of-string "\0" */
    if (send(fd, buf, (len+1 < sizeof(buf)) ? len+1 : sizeof(buf), 0) < 0) {
        error_e("error in send()");
    }
}


#define Test_line(LINE, PID, INDEX, UNTIL) \
            { \
                if (all || strcmp(user, LINE->cl_file->cf_user) == 0 ) { \
		    print_line(fd, LINE, fields, PID, INDEX, UNTIL); \
		    found = 1; \
                } \
            }
void
cmd_ls(struct fcrondyn_cl *client, long int *cmd, int fd, int is_root)
    /* run a command which lists some jobs */
{
    int found = 0;
    int all = (cmd[1] == ALL) ? 1 : 0;
    char *user = NULL;
    struct job_t *j;
    int i;
    unsigned char fields[FIELD_NUM_SIZE];
    exe_t *e = NULL;
    lavg_t *l = NULL;

    for (i = 0; i < FIELD_NUM_SIZE; i++)
        fields[i] = 0;

    switch (cmd[0]) {
    case CMD_DETAILS:
        bit_set(fields, FIELD_SCHEDULE);
        bit_set(fields, FIELD_RQ);
        bit_set(fields, FIELD_USER);
        bit_set(fields, FIELD_OPTIONS);
        print_fields(fd, fields);
        for (j = queue_base; j != NULL; j = j->j_next) {
            if (cmd[1] == j->j_line->cl_id) {
                if (strcmp(client->fcl_user, j->j_line->cl_file->cf_user) == 0
                    || is_root)
                    print_line(fd, j->j_line, fields, 0, 0, 0);
                else
                    Send_err_msg(fd, err_job_nfound_str);
                found = 1;
                break;
            }
        }
        break;

    case CMD_LIST_JOBS:
    case CMD_LIST_LAVGQ:
    case CMD_LIST_SERIALQ:
    case CMD_LIST_EXEQ:
        if (cmd[0] == CMD_LIST_LAVGQ) {
            double lavg[3] = { 0, 0, 0 };
            char lavg_str[TERM_LEN];
            getloadavg(lavg, 3);
            i = snprintf(lavg_str, sizeof(lavg_str), "Current load average : "
                         "%.1f, %.1f, %.1f\n", lavg[0], lavg[1], lavg[2]);
            send(fd, lavg_str, (i+1 < sizeof(lavg_str))? i+1 : sizeof(lavg_str), 0);

            bit_set(fields, FIELD_LAVG);
        }
        else
            bit_set(fields, FIELD_SCHEDULE);

        if (cmd[0] == CMD_LIST_SERIALQ)
            bit_set(fields, FIELD_INDEX);

        if (cmd[0] == CMD_LIST_EXEQ)
            bit_set(fields, FIELD_PID);

        if (all && !is_root) {
            warn("User %s tried to list *all* jobs.", client->fcl_user);
            Send_err_msg_end(fd, err_all_nallowed_str);
            return;
        }
        if (all)
            bit_set(fields, FIELD_USER);
        print_fields(fd, fields);

        if (!all) {
            struct passwd *pass;

#ifdef SYSFCRONTAB
            if (cmd[1] == SYSFCRONTAB_UID)
                user = SYSFCRONTAB;
            else {
#endif
                if ((pass = getpwuid((uid_t) cmd[1])) == NULL) {
                    warn_e("Unable to find passwd entry for %ld", cmd[1]);
                    Send_err_msg_end(fd, err_invalid_user_str);
                    return;
                }
                if (!is_root && strcmp(pass->pw_name, client->fcl_user) != 0) {
                    warn_e("%s is not allowed to see %s's jobs. %ld",
                           client->fcl_user, pass->pw_name);
                    Send_err_msg_end(fd, err_others_nallowed_str);
                    return;
                }
                user = pass->pw_name;
#ifdef SYSFCRONTAB
            }
#endif
        }

        /* list all jobs one by one and find the corresponding ones */
        switch (cmd[0]) {
        case CMD_LIST_JOBS:
            for (j = queue_base; j != NULL; j = j->j_next)
                Test_line(j->j_line, 0, 0, 0);
            break;

        case CMD_LIST_EXEQ:
            for (e = exe_list_first(exe_list); e != NULL;
                 e = exe_list_next(exe_list)) {
                if (e->e_line == NULL) {
                    if (is_root) {
                        send_msg_fd(fd, "Job no more in an fcrontab: pid %d",
                                    e->e_job_pid);
                        found = 1;
                    }
                }
                else {
                    Test_line(e->e_line, e->e_job_pid, 0, 0);
                }
            }
            break;

        case CMD_LIST_LAVGQ:
            for (l = lavg_list_first(lavg_list); l != NULL;
                 l = lavg_list_next(lavg_list))
                Test_line(l->l_line, 0, 0, l->l_until);
            break;

        case CMD_LIST_SERIALQ:
            {
                int j;
                i = serial_array_index;
                for (j = 0; j < serial_num; j++) {
                    Test_line(serial_array[i], 0, j, 0);
                    if (++i >= serial_array_size)
                        i -= serial_array_size;
                }
                break;
            }

        }

        break;
    }

    if (!found)
        Send_err_msg(fd, err_job_nfound_str);
    /* to tell fcrondyn there's no more data to wait */
    Tell_no_more_data(fd);

}


void
cmd_on_exeq(struct fcrondyn_cl *client, long int *cmd, int fd, int is_root)
/* common code to all cmds working on jobs in the exeq */
{
    int found = 0;
    char *err_str = NULL;
    exe_t *e = NULL;

    /* find the corresponding job */
    for (e = exe_list_first(exe_list); e != NULL; e = exe_list_next(exe_list)) {
        if (e->e_line != NULL && cmd[2] == e->e_line->cl_id) {

            found = 1;

            /* check if the request is valid */
            if (!is_root &&
                strcmp(client->fcl_user, e->e_line->cl_file->cf_user) != 0) {

                if (cmd[0] == CMD_RENICE)
                    err_str = "%s tried to renice to %ld job id %ld for %s : "
                        "not allowed.";
                else if (cmd[0] == CMD_SEND_SIGNAL)
                    err_str = "%s tried to send signal %ld to id %ld for %s : "
                        "not allowed.";
                else
                    err_str = "cannot run unknown cmd with arg %ld on job id "
                        "%ld for %s : not allowed.";

                warn(err_str, client->fcl_user, cmd[1], cmd[2],
                     client->fcl_user);
                Send_err_msg_end(fd, err_job_nfound_str);
            }
            else {
                /* request is valid : do it */

                if (cmd[0] == CMD_SEND_SIGNAL)
                    cmd_send_signal(client, cmd, fd, e);
                else if (cmd[0] == CMD_RENICE)
                    cmd_renice(client, cmd, fd, e, is_root);
                else {
                    Send_err_msg_end(fd, err_cmd_unknown_str);
                    exe_list_end_iteration(exe_list);
                    return;
                }
            }
        }
    }

    if (!found) {

        if (cmd[0] == CMD_RENICE)
            err_str = "cannot renice job id %ld for %s : no corresponding "
                "running job.";
        else if (cmd[0] == CMD_SEND_SIGNAL)
            err_str = "cannot send signal to job id %ld for %s :"
                " no corresponding running job.";
        else
            err_str = "cannot run unknown cmd on job id %ld for %s :"
                " no corresponding running job.";

        warn(err_str, cmd[2], client->fcl_user);
        Send_err_msg_end(fd, err_rjob_nfound_str);
    }
    else {
        Tell_no_more_data(fd);
    }

}


void
cmd_renice(struct fcrondyn_cl *client, long int *cmd, int fd, exe_t * e,
           int is_root)
/* change nice value of a running job */
{

#ifdef HAVE_SETPRIORITY
    /* check if arguments are valid */
    if (e->e_job_pid <= 0 || ((int)cmd[1] < 0 && !is_root)
        || (int)cmd[1] > 20 || (int)cmd[1] < -20) {
        warn("renice: invalid args : pid: %d nice_value: %d user: %s.",
             e->e_job_pid, (int)cmd[1], client->fcl_user);
        Send_err_msg_end(fd, err_invalid_args_str);
        return;
    }

    /* ok, now setpriority() the job */
    if (setpriority(PRIO_PROCESS, e->e_job_pid, (int)cmd[1]) != 0) {
        error_e("could not setpriority(PRIO_PROCESS, %d, %d)",
                e->e_job_pid, (int)cmd[1]);
        Send_err_msg_end(fd, err_unknown_str);
        return;
    }
    else {
        send_msg_fd(fd, "Command successfully completed on process %d.",
                    e->e_job_pid);
        return;
    }

#else                           /* HAVE_SETPRIORITY */
    warn("System has no setpriority() : cannot renice. pid: %d nice_value: %d user: %s.", e->e_job_pid, (int)cmd[1], client->fcl_user);
    Send_err_msg_end(fd, err_cmd_unknown_str);

#endif                          /* HAVE_SETPRIORITY */
}


void
cmd_send_signal(struct fcrondyn_cl *client, long int *cmd, int fd, exe_t * e)
/* send a signal to a running job */
{
    if (e->e_job_pid <= 0 || (int)cmd[1] <= 0) {
        warn("send_signal: invalid args : pid: %d signal: %d user: %s",
             e->e_job_pid, (int)cmd[1], client->fcl_user);
        Send_err_msg_end(fd, err_invalid_args_str);
        return;
    }

    /* ok, now kill() the job */
    if (kill(e->e_job_pid, (int)cmd[1]) != 0) {
        error_e("could not kill(%d, %d)", e->e_job_pid, (int)cmd[1]);
        Send_err_msg_end(fd, err_unknown_str);
        return;
    }
    else {
        send_msg_fd(fd, "Command successfully completed on process %d.",
                    e->e_job_pid);
        return;
    }
}


void
cmd_run(struct fcrondyn_cl *client, long int *cmd, int fd, int is_root)
    /* Run a job and reschedule if requested  */
{

    struct job_t *j = NULL;

    for (j = queue_base; j != NULL; j = j->j_next) {
        if (cmd[1] == j->j_line->cl_id) {
            if (strcmp(client->fcl_user, j->j_line->cl_file->cf_user) == 0
                || is_root) {

                if (is_lavg(j->j_line->cl_option))
                    add_lavg_job(j->j_line, fd);
                else if (is_serial(j->j_line->cl_option))
                    add_serial_job(j->j_line, fd);
                else
                    run_normal_job(j->j_line, fd);

                if (cmd[0] == CMD_RUNNOW)
                    set_next_exe(j->j_line, FROM_CUR_NEXTEXE, fd);

                Tell_no_more_data(fd);

                return;

            }
        }
    }

    /* we won't get here if a job has been found */
    Send_err_msg_end(fd, err_job_nfound_str);

}

void
exe_cmd(struct fcrondyn_cl *client)
    /* read command, and call corresponding function */
{
    long int *cmd;
    int fd;
    int is_root = 0;

    is_root = (strcmp(client->fcl_user, ROOTNAME) == 0) ? 1 : 0;

    /* to make things clearer (avoid repeating client->fcl_ all the time) */
    cmd = client->fcl_cmd;
    fd = client->fcl_sock_fd;

    /* */
    debug("exe_cmd [0,1,2] : %d %d %d", cmd[0], cmd[1], cmd[2]);
    /* */

    switch (cmd[0]) {

    case CMD_SEND_SIGNAL:
    case CMD_RENICE:
        cmd_on_exeq(client, cmd, fd, is_root);
        break;

    case CMD_DETAILS:
    case CMD_LIST_JOBS:
    case CMD_LIST_LAVGQ:
    case CMD_LIST_SERIALQ:
    case CMD_LIST_EXEQ:
        cmd_ls(client, cmd, fd, is_root);
        break;

    case CMD_RUN:
    case CMD_RUNNOW:
        cmd_run(client, cmd, fd, is_root);
        break;

    default:
        Send_err_msg_end(fd, err_cmd_unknown_str);
    }
}

void
remove_connection(struct fcrondyn_cl **client, struct fcrondyn_cl *prev_client,
                  select_instance * si)
/* close the connection, remove it from the list
and make client points to the next entry */
{
    debug("closing connection : fd : %d", (*client)->fcl_sock_fd);
    shutdown((*client)->fcl_sock_fd, SHUT_RDWR);
    select_rm_read(si, (*client)->fcl_sock_fd);
    xclose_check(&((*client)->fcl_sock_fd), "client fd");
    if (prev_client == NULL) {
        fcrondyn_cl_base = (*client)->fcl_next;
        Free_safe((*client)->fcl_user);
        Free_safe(*client);
        *client = fcrondyn_cl_base;
    }
    else {
        prev_client->fcl_next = (*client)->fcl_next;
        Free_safe((*client)->fcl_user);
        Free_safe(*client);
        *client = prev_client->fcl_next;
    }
    fcrondyn_cl_num -= 1;
}

void
fcrondyn_socket_check(select_instance * si)
    /* check for new connection, command, connection closed */
{
    int fd = -1, avoid_fd = -1;
    socklen_t addr_len = sizeof(struct sockaddr_un);
    struct sockaddr_un client_addr;
    long int buf_int[SOCKET_MSG_LEN];
    int read_len = 0;
    struct fcrondyn_cl *client = NULL, *prev_client = NULL;

    if (si->retcode <= 0)
        /* no socket to check : go directly to the end of that function */
        return;

    debug("Checking socket ...");

    if (FD_ISSET(listen_fd, &si->readfds)) {
        debug("got new connection ...");
        fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addr_len);
        if (fd == -1) {
            error_e
                ("could not accept new connection : isset(listen_fd = %d) = %d",
                 listen_fd, FD_ISSET(listen_fd, &si->readfds));
        }
        else {
            fcntl(fd, F_SETFD, 1);
            /* set fd to O_NONBLOCK : we do not want fcron to be stopped on error, etc */
            if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
                error_e
                    ("Could not set fd attribute O_NONBLOCK : connection rejected.");
                shutdown(fd, SHUT_RDWR);
                xclose_check(&fd, "client fd");
            }
            else {
                Alloc(client, fcrondyn_cl);
                client->fcl_sock_fd = fd;
                /* means : not authenticated yet : */
                client->fcl_user = NULL;
                client->fcl_cmd = NULL;

                /* include new entry in client list */
                client->fcl_next = fcrondyn_cl_base;
                fcrondyn_cl_base = client;
                client->fcl_idle_since = now;
                /* to avoid trying to read from it in this call */
                avoid_fd = fd;

                select_add_read(si, fd);
                fcrondyn_cl_num += 1;

                debug("Added connection fd : %d - %d connections", fd,
                      fcrondyn_cl_num);

#ifdef SO_PEERCRED
                auth_client_so_peercred(client);
#elif defined(HAVE_GETPEERUCRED) || defined(HAVE_GETPEEREID)
                auth_client_getpeer(client);
#endif                          /* SO_PEERCRED */
            }
        }
    }

    client = fcrondyn_cl_base;
    while (client != NULL) {
        if (!FD_ISSET(client->fcl_sock_fd, &si->readfds)
            || client->fcl_sock_fd == avoid_fd) {
            /* check if the connection has not been idle for too long ... */
            if (client->fcl_user == NULL
                && now - client->fcl_idle_since > MAX_AUTH_TIME) {
                warn("Connection with no auth for more than %ds : closing it.",
                     MAX_AUTH_TIME);
                remove_connection(&client, prev_client, si);
            }
            else if (now - client->fcl_idle_since > MAX_IDLE_TIME) {
                warn("Connection of %s is idle for more than %ds : closing it.",
                     client->fcl_user, MAX_IDLE_TIME);
                remove_connection(&client, prev_client, si);
            }
            else {
                /* nothing to do on this one ... check the next one */
                prev_client = client;
                client = client->fcl_next;
            }
            continue;
        }

        if ((read_len =
             recv(client->fcl_sock_fd, buf_int, sizeof(buf_int), 0)) <= 0) {
            if (read_len == 0) {
                /* connection closed by client */
                remove_connection(&client, prev_client, si);
            }
            else {
                error_e("error recv() from sock fd %d", client->fcl_sock_fd);
                prev_client = client;
                client = client->fcl_next;
            }
        }
        else {
            client->fcl_cmd_len = read_len;
            client->fcl_cmd = buf_int;
            if (client->fcl_user == NULL)
                /* not authenticated yet */
                auth_client_password(client);
            else {
                /* we've just read a command ... */
                client->fcl_idle_since = now;
                exe_cmd(client);
            }
            prev_client = client;
            client = client->fcl_next;
        }
    }

}


void
fcrondyn_socket_close(select_instance * si)
    /* close connections, close socket, remove socket file.
     * If si is not NULL, then remove the fds from si's readfds */
{
    struct fcrondyn_cl *client, *client_buf = NULL;

    if (listen_fd) {
        shutdown(listen_fd, SHUT_RDWR);
        xclose_check(&listen_fd, "listening fd");
        unlink(fifofile);

        client = fcrondyn_cl_base;
        while (client != NULL) {
            shutdown(client->fcl_sock_fd, SHUT_RDWR);
            xclose_check(&(client->fcl_sock_fd), "client fd");
            if (si != NULL) {
                select_rm_read(si, client->fcl_sock_fd);
            }

            client_buf = client->fcl_next;
            Free_safe(client);
            fcrondyn_cl_num -= 1;
            client = client_buf;
        }
    }
}
