/* $Cambridge: hermes/src/prayer/accountd/os_linux.c,v 1.4 2004/10/01 13:25:51 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2002 */
/* See the file NOTICE for conditions of use and distribution. */

#include "accountd.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <wait.h>
#include <netdb.h>
#include <sys/file.h>
#include <stropts.h>

#ifdef USE_SSL
#include <openssl/rand.h>
#endif

#ifdef USE_BSD_PTY
/* Use BSD terminal stuff: simpler */
#include <pty.h>                /* for openpty and forkpty */
#include <utmp.h>               /* for login_tty */
#endif

/* ====================================================================== */

BOOL os_socketpair(int *sockfd)
{
    int rc;

    do {
        rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
    } while ((rc < 0) && (errno == EINTR));

    return ((rc == 0) ? T : NIL);
}

/* ====================================================================== */

int os_connect_unix_socket(char *name)
{
    struct sockaddr_un serv_addr;
    int sockfd, servlen;

    /* Open the socket */

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return (-1);

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, name);
    servlen = sizeof(serv_addr);

    if (connect(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0) {
        close(sockfd);
        return (-1);
    }

    return (sockfd);
}

int os_connect_inet_socket(char *host, unsigned long port)
{
    struct hostent *hostent;
    struct sockaddr_in serv_addr;
    int sockfd;

    /* Open the socket */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return (-1);

    /* Set up the socket */
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);

    if ((hostent = gethostbyname(host)) == NIL) {
        close(sockfd);
        return (-1);
    }
    bcopy(hostent->h_addr, (char *) &serv_addr.sin_addr,
          hostent->h_length);

    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
        < 0) {
        close(sockfd);
        return (-1);
    }

    return (sockfd);
}

/* ====================================================================== */

int os_bind_unix_socket(char *name)
{
    struct sockaddr_un serv_addr;
    int sockfd, servlen;
    int i;

    /* Generate well known connect address for frontend servers */
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;

    strcpy(serv_addr.sun_path, name);
    servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
    unlink(serv_addr.sun_path);

    /* Open the socket */
    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        log_panic("[os_bind_unix_socket()] socket() failed, %s",
                  strerror(errno));
        return (-1);
    }

    /* Set socket reuseaddr, otherwise bind will fail after fast stop/start */
    i = 1;
    if (setsockopt
        (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &i, sizeof(int))) {
        log_panic(("[os_bind_unix_socket()] setsockopt() failed: "
                   "couldn't set SO_REUSEADDR on init port, %s"),
                  strerror(errno));
        close(sockfd);
        return (-1);
    }

    /* bind() as UNIX domain socket */
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <
        0) {
        log_panic("[os_bind_unix_socket()] bind() failed: %s",
                  strerror(errno));
        close(sockfd);
        return (-1);
    }

    /* Requests should queue on sockfd until we are ready to serve them */
    if (listen(sockfd, 10) < 0) {
        log_panic("[os_bind_unix_socket()] listen() failed, %s",
                  strerror(errno));
        close(sockfd);
        return (-1);
    }
    return (sockfd);
}

/* ====================================================================== */

int os_bind_inet_socket(unsigned long port)
{
    int i, sockfd;
    struct sockaddr_in serv_addr;

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        log_panic("[os_bind_unix_socket()] socket() failed, %s",
                  strerror(errno));
        return (-1);
    }

    /* Set Reuseaddr on this socket */
    i = 1;
    if (setsockopt
        (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &i, sizeof(int))) {
        log_panic(("[os_bind_inet_socket()] setsockopt() failed: "
                   "couldn't set SO_REUSEADDR on init port, %s"),
                  strerror(errno));
        close(sockfd);
        return (-1);
    }

    /* bind() as Internet domain socket */

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <
        0) {
        log_panic("[os_bind_inet_socket()] bind() failed: %s",
                  strerror(errno));
        close(sockfd);
        return (-1);
    }

    if (listen(sockfd, 10) < 0) {
        log_panic("[os_bind_inet_socket()] listen() failed, %s",
                  strerror(errno));
        close(sockfd);
        return (-1);
    }

    return (sockfd);
}

/* ====================================================================== */

int os_accept_unix(int sockfd)
{
    struct sockaddr_un addr;
    socklen_t len = (socklen_t) sizeof(struct sockaddr_un);
    int newsockfd;

    do {
        newsockfd = accept(sockfd, (struct sockaddr *) &addr, &len);
    } while ((newsockfd < 0) && (errno == EINTR));

    if (newsockfd < 0) {
        log_panic("[os_accept_unix()] accept() failed: %s",
                  strerror(errno));
        close(newsockfd);
        return (-1);
    }

    /* Set close on exec so subprocesses can't interfere */
    if (fcntl(newsockfd, F_SETFD, FD_CLOEXEC) < 0) {
        log_panic("[os_accept_unix()] fcntl() (close-on-exec) failed:  %s",
                  strerror(errno));
        close(newsockfd);
        return (-1);
    }

    return (newsockfd);
}

int os_accept_inet(int sockfd, struct ipaddr *ipaddr)
{
    struct sockaddr_in addr;
    socklen_t len = (socklen_t) sizeof(struct sockaddr_in);
    int newsockfd;

    do {
        newsockfd = accept(sockfd, (struct sockaddr *) &addr, &len);
    } while ((newsockfd < 0) && (errno == EINTR));

    if (newsockfd < 0) {
        log_panic("[os_accept_inet()] accept() failed: %s",
                  strerror(errno));
        close(newsockfd);
        return (-1);
    }

    if (ipaddr)
        ipaddr_set(ipaddr, 4, (unsigned char *) &addr.sin_addr);

    /* Set close on exec so subprocesses can't interfere */
    if (fcntl(newsockfd, F_SETFD, FD_CLOEXEC) < 0) {
        log_panic("[os_accept_inet()] fcntl() (close-on-exec) failed:  %s",
                  strerror(errno));
        close(newsockfd);
        return (-1);
    }

    return (newsockfd);
}

/* ====================================================================== */

/* Run child outside PTY */
BOOL os_run(char *cmdline, int *fdp, int *childpidp)
{
    int fd[2];
    int pid;

    if (!os_socketpair(fd))
        return(NIL);

    if ((pid = fork()) < 0)
        return (NIL);

    if (pid == 0) {
        dup2(fd[1], 0);
        dup2(fd[1], 1);
        dup2(fd[1], 2);

        close(fd[0]);
        close(fd[1]);

        process_exec(cmdline);
        /* NOTREACHED */
        exit(1);
    }

    /* Parent process */
    close(fd[1]);
    *fdp = fd[0];
    *childpidp = pid;
    return (T);
}

#ifdef USE_BSD_PTY
/* BSD Psuedo-Terminal support */
BOOL os_run_pty(char *cmdline, int *fdp, int *childpidp)
{
    int fd;
    int pid;

    if ((pid = forkpty(&fd, NIL, NIL, NIL)) < 0)
        return (NIL);

    if (pid == 0) {
        process_exec(cmdline);
        /* NOTREACHED */
        exit(1);
    }

    /* Parent process */
    *fdp = fd;
    *childpidp = pid;
    return (T);
}
#else
/* System V Pseudo-Terminal support */

/* PTY handling code inferrred from OpenSSH and Solaris 8 in.telnetd code
 * Conclusion: PTY stuff is awful! */

BOOL os_run_pty(char *cmdline, int *fdp, int *childpidp)
{
    extern int grantpt();       /* Provide prototypes for pty fns */
    extern int unlockpt();      /* Linux hdrs don't define these properly */
    extern char *ptsname();
    char *slavename;
    int masterfd, slavefd;
    int pid;

    *fdp = (-1);
    *childpidp = 0;

    masterfd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (masterfd < 0) {
        close(masterfd);
        log_misc("/dev/ptmx: %s", strerror(errno));
        return (NIL);
    }

    if (grantpt(masterfd) < 0) {
        close(masterfd);
        log_misc("grantpt: %s", strerror(errno));
        return (NIL);
    }

    if (unlockpt(masterfd) < 0) {
        close(masterfd);
        log_misc("unlockpt: %s", strerror(errno));
        return (NIL);
    }

    if ((pid = fork()) < 0)
        log_fatal("fork() failed");

    if (pid == 0) {
        /* Child process */
        slavename = ptsname(masterfd);

        if (slavename == NIL) {
            close(masterfd);
            log_misc("Slave pty side name could not be obtained.");
            return (NIL);
        }

        /* Put child process in its own session group */
        /* Removes controlling terminal */
        (void) setsid();

        /* Open slave side of pty and dup to become stdin, stdout, stderr */
        /* slavefd becomes new controlling terminal for child process     */
        if ((slavefd = open(slavename, O_RDWR)) < 0)
            log_fatal("Couldn't open slave");

        close(masterfd);

        if (isastream(slavefd)) {
            if (ioctl(slavefd, I_PUSH, "ptem") < 0)
                log_fatal("ioctl I_PUSH ptem: %s", strerror(errno));

            if (ioctl(slavefd, I_PUSH, "ldterm") < 0)
                log_fatal("ioctl I_PUSH ldterm: %s", strerror(errno));

            if (ioctl(slavefd, I_PUSH, "ttcompat") < 0)
                log_fatal("ioctl I_PUSH ttcompat: %s", strerror(errno));
        }
#if 0                           /* XXX Testing terminal control: something funny going on here...  */
        {
            struct termios attr;

            if (tcgetattr(slavefd, &attr) != 0)
                log_fatal("tcgetattr() failed");

            attr.c_oflag &= ~(OPOST | IXOFF | IXON);
            attr.c_lflag &= ~(ISIG | ECHO | XCASE);     /* ICANON? */
            attr.c_lflag |= ECHONL;
            attr.c_cflag &= ~(CSIZE | PARENB);
            attr.c_cflag |= CS8;
            attr.c_cc[VMIN] = 1;
            attr.c_cc[VTIME] = 1;

            if (tcsetattr(slavefd, TCSANOW, &attr) != 0)
                log_fatal("tcgetattr() failed");
        }
#endif

        dup2(slavefd, 0);
        dup2(slavefd, 1);
        dup2(slavefd, 2);
        close(slavefd);

        process_exec(cmdline);
        /* NOTREACHED */
        exit(1);
    }

    *fdp = masterfd;
    *childpidp = pid;
    return (T);
}
#endif

/* ====================================================================== */

/* Trivial SIG_CLD handler to prevent zombies from hanging around */

void os_child_reaper()
{
    int status;
    pid_t child;

    do {
        child = waitpid(0, &status, WNOHANG);
    } while (child > 0);
}

pid_t os_waitpid_nohang()
{
    int status;

    return (waitpid(0, &status, WNOHANG));
}

BOOL os_signal_child_init(void (*fn) ())
{
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_handler = fn;
    act.sa_flags = 0;

    if (sigaction(SIGCHLD, &act, &oact) == 0)
        return (T);

    log_panic("[os_signal_child_init()] sigaction() failed: %s",
              strerror(errno));
    return (NIL);
}

BOOL os_signal_child_clear(void)
{
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_handler = SIG_DFL;
    act.sa_flags = 0;

    if (sigaction(SIGCHLD, &act, &oact) == 0)
        return (T);

    log_panic("[os_signal_child_clear()] sigaction() failed: %s",
              strerror(errno));
    return (NIL);
}

/* ====================================================================== */

BOOL os_signal_alarm_init(void (*fn) ())
{
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_handler = fn;
    act.sa_flags = 0;

    if (sigaction(SIGALRM, &act, &oact) == 0)
        return (T);

    log_panic("[os_signal_alarm_init()] sigaction() failed: %s",
              strerror(errno));
    return (NIL);
}

BOOL os_signal_alarm_clear(void)
{
    struct sigaction act, oact;

    sigemptyset(&act.sa_mask);
    act.sa_handler = SIG_DFL;
    act.sa_flags = 0;

    if (sigaction(SIGALRM, &act, &oact) == 0)
        return (T);

    log_panic("[os_signal_alarm_clear()] sigaction() failed: %s",
              strerror(errno));
    return (NIL);
}

/* ====================================================================== */

BOOL os_lock_exclusive(int fd)
{
    int rc;

    do {
        rc = flock(fd, LOCK_EX);
    } while ((rc < 0) && (errno == EINTR));

    return ((rc >= 0) ? T : NIL);
}

BOOL os_lock_shared(int fd)
{
    int rc;

    do {
        rc = flock(fd, LOCK_SH);
    } while ((rc < 0) && (errno == EINTR));

    return ((rc >= 0) ? T : NIL);
}

BOOL os_lock_release(int fd)
{
    int rc;

    do {
        rc = flock(fd, LOCK_UN);
    } while ((rc < 0) && (errno == EINTR));

    return ((rc >= 0) ? T : NIL);
}

BOOL os_lock_exclusive_allow_break(int fd)
{
    return ((flock(fd, LOCK_EX) >= 0) ? T : NIL);
}

BOOL os_lock_shared_allow_break(int fd)
{
    return ((flock(fd, LOCK_SH) >= 0) ? T : NIL);
}

BOOL os_lock_release_allow_break(int fd)
{
    return ((flock(fd, LOCK_UN) >= 0) ? T : NIL);
}

/* ====================================================================== */

static int os_read(int fd, char *buf, unsigned long count)
{
    int rc;

    while (count > 0) {
        rc = read(fd, buf, count);

        if (rc > 0) {
            buf += rc;
            count -= rc;
        } else if (rc == 0)     /* EOF */
            break;
        else if (errno != EINTR)        /* read() failed */
            break;
    }

    return ((count == 0) ? T : NIL);
}

BOOL os_random(struct config * config, void *buffer, unsigned long count)
{
    int fd;

    if (config->egd_socket) {
        if ((fd = os_connect_unix_socket(config->egd_socket)) < 0)
            return (NIL);

        while (count > 0) {
            unsigned char buf[2];
            unsigned long bytes = (count > 255) ? (count % 256) : count;

            buf[0] = 0x02;
            buf[1] = (unsigned char) bytes;

            if (!((write(fd, buf, 2) == 2) && os_read(fd, buffer, bytes))) {
                close(fd);
                return (NIL);
            }
            count -= bytes;
        }

        close(fd);
        return (T);
    }

    if ((fd = open("/dev/urandom", O_RDONLY)) < 0)
        return (NIL);

    if (!os_read(fd, buffer, count)) {
        close(fd);
        return (NIL);
    }

    close(fd);
    return (T);
}


syntax highlighted by Code2HTML, v. 0.9.1