/* Copyright 2004 Red Hat, Inc.
 *
 * Licensed 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.
 */

/* SSLPassPhraseDialog tool: sent prompt text on stdin, sends back
 * passwords on stdout; forwards both to/from /dev/tty.  Written as a
 * standalone executable to: (a) allow running within a separate
 * SELinux security context; (b) enforce a 5 minute timeout to prevent
 * httpd from hanging indefinitely if configured to start at boot.  */

/* Written by Joe Orton; file bugs against the httpd component in
 * https://bugzilla.redhat.com/bugzilla/ */

#include <sys/stat.h>
#include <sys/poll.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include <errno.h>

static int tty;
static struct termios torig;

#define TIMEOUT_MSG "...timing out after 5 minutes.\n"
#define TIMEOUT (5 * 60)

/* interrupt signal handler */
static void interrupt(int signum)
{
    tcsetattr(tty, TCSANOW, &torig);
    _exit(6);
}

static int full_write(int fd, char *buffer, size_t len)
{
    ssize_t nb;

    do {
        nb = write(fd, buffer, len);
        if (nb == -1 && errno == EINTR) continue;
        
        buffer += nb;
        len -= nb;
    } while (nb > 0 && len);

    return len ? -1 : 0;
}

/* Await input from the tty (passwords to send to mod_ssl), or stdin
 * (output to send to the tty. */
static int pollit(void)
{
    struct pollfd fds[2];
    int rv;

    fds[0].fd = tty;
    fds[1].fd = STDIN_FILENO;

    fds[0].events = fds[1].events = POLLIN | POLLHUP;

    do {
        fds[0].revents = fds[1].revents = 0;

        rv = poll(fds, 2, TIMEOUT * 1000);

        if (rv == -1 && errno == EINTR)
            continue;

        if (rv > 0) {
            int from, to;
            ssize_t len;
            char buffer[BUFSIZ]; 

            if (fds[1].revents & (POLLIN|POLLHUP)) {
                from = STDIN_FILENO;
                to = tty;
            } else {
                from = tty;
                to = STDOUT_FILENO;
            }

            len = read(from, buffer, sizeof buffer);
            if (len < 1)
                return 0;

            if (full_write(to, buffer, len))
                return 1;
        }
        
    } while (rv > 0);

    if (rv == 0) {
        /* timeout */
        full_write(tty, TIMEOUT_MSG, strlen(TIMEOUT_MSG));
        tcdrain(tty);
        rv = 1;
    }

    return rv;
}

int main(int argc, char **argv)
{
    struct termios tio;
    int rv;

    /* open the tty */
    tty = open("/dev/tty", O_RDWR);
    if (tty < 0)
        exit(2);

    if (tcgetattr(tty, &torig) < 0)
        exit(3);

    tio = torig;
    tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
    tio.c_lflag |= ICANON;

    signal(SIGTERM, interrupt);
    signal(SIGINT, interrupt);
    signal(SIGHUP, interrupt);
    signal(SIGPIPE, interrupt);

    if (tcsetattr(tty, TCSAFLUSH, &tio) < 0)
        exit(3);

    rv = pollit() ? 4 : 0;
    
    tcsetattr(tty, TCSAFLUSH, &torig);
    exit(rv);
}

