/*
 * Copyright (c) 2004 Seth Kingsley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *    This product includes software developed by Seth Kingsley and
 *    contributors.
 * 4. Neither the author's name, nor any of the contributors names may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include    <sys/cdefs.h>
#include    <sys/types.h>
#include    <sys/wait.h>
#include    <sys/errno.h>

#include    <gtk/gtkmain.h>

#include    <libutil.h>
#include    <unistd.h>
#include    <signal.h>
#include    <termios.h>
#include    <pwd.h>
#include    <stdio.h>
#include    <string.h>
#include    <sysexits.h>
#include    <err.h>

#include    <xmk-gtk.h>

#include    "gtk-su-gui.h"
#include    "support.h"

__RCSID("$Id: gtk-su.c,v 1.10 2004/06/07 23:14:26 sethk Exp $");

#define SU_CMD	"/usr/bin/su"
#define TTY_EOL	"\r\n"

struct data
{
    GtkDialog	*d_dialog;
    GtkEntry	*d_entry;
    pid_t	d_ptypid;
    int		d_ptyfd;
    char	d_eofch;
    char	d_lastline[BUFSIZ + 1];
    size_t	d_lastlen;
    gboolean	d_eol;
    gboolean	d_didauth;
};

static void
_pty_input(struct data *dp, int fd, GdkInputCondition cond)
{
    char    buf[BUFSIZ + 1];
    ssize_t br;

    if ((br = read(fd, buf, sizeof(buf) - 1)) <= 0)
    {
	if (br == -1)
	{
	    kill(dp->d_ptypid, SIGTERM);
	    xmk_error(NULL, EX_OSERR, TRUE, _("PTY read error"));
	}
#ifdef DEBUG_PTY
	warnx("pty closed\n");
#endif /* DEBUG_PTY */
	close(fd);
	gtk_main_quit();
	return;
    }

    buf[br] = '\0';

#ifdef DEBUG_PTY
    warnx("pty input \"%s\"", buf);
#endif /* DEBUG_PTY */

    if (!dp->d_didauth)
    {
	if (!strcmp(buf, "Password:"))
	{
	    char    resp[128 + 1 + 1];

	    switch (gtk_dialog_run(dp->d_dialog))
	    {
		case GTK_RESPONSE_OK:
		    snprintf(resp, sizeof(resp), "%s\n",
			    gtk_entry_get_text(dp->d_entry));
		    write(fd, resp, strlen(resp));

		    break;

		case GTK_RESPONSE_CANCEL:
		case GTK_RESPONSE_CLOSE:
		case GTK_RESPONSE_DELETE_EVENT:
		    resp[0] = dp->d_eofch;
		    write(fd, resp, 1);

		    break;
	    }

	    dp->d_didauth = TRUE;
	    return;
	}
    }
    else
	write(STDOUT_FILENO, buf, br);

    /* Clear any previous line. */
    if (dp->d_eol)
	dp->d_lastlen = 0;

    /* Concatenate output until we reach newline. */
    dp->d_lastlen+= strlcpy(dp->d_lastline + dp->d_lastlen,
	    buf,
	    (sizeof(dp->d_lastline) - 1) - dp->d_lastlen);

    /* Chomp newline and mark EOL. */
    if (!strcmp((dp->d_lastline + dp->d_lastlen) -
		(sizeof(TTY_EOL) - 1),
		TTY_EOL))
    {
	char	*nl;

	dp->d_lastlen-= 2;
	dp->d_lastline[dp->d_lastlen] = '\0';
	/* XXX: shift out any extra lines. */
	while ((nl = strstr(dp->d_lastline, TTY_EOL)))
	{
	    dp->d_lastlen-= nl - dp->d_lastline + (sizeof(TTY_EOL) - 1);
	    bcopy(nl + (sizeof(TTY_EOL) - 1),
		    dp->d_lastline,
		    dp->d_lastlen + 1);
	}
	dp->d_eol = TRUE;
    }
}

static void
_tty_input(struct data *dp, int fd, GdkInputCondition cond)
{
    char    buf[BUFSIZ];
    ssize_t br;

    if ((br = read(fd, buf, sizeof(buf))) <= 0)
    {
	/*
	if (br == -1)
	    xmk_warning(NULL, TRUE, "read from terminal");
	 */

	close(dp->d_ptyfd);
	gtk_main_quit();
    }
    else if (dp->d_didauth)
	if (write(dp->d_ptyfd, buf, br) < br)
	{
	    kill(dp->d_ptypid, SIGTERM);
	    xmk_error(NULL, EX_IOERR, TRUE, _("PTY write error"));
	}
}

int
main(int ac, char *av[])
{
    struct data	d;
    int		st;
    extern int	optind;
    extern int	opterr;
    GtkLabel	*label;
    const char	*lfmt;
    char	lstr[512 + 1],
		cwd[PATH_MAX];

    xmk_gtk_init(&ac, &av);
    d.d_dialog = GTK_DIALOG(create_su_dlg());
    d.d_entry = GTK_ENTRY(lookup_widget(GTK_WIDGET(d.d_dialog), "entry"));
    d.d_lastlen = 0;
    d.d_eol = d.d_didauth = FALSE;

    opterr = 0;
    while (getopt(ac, av, "c:") != -1)
	;

    /* Set the user label */
    label = GTK_LABEL(lookup_widget(GTK_WIDGET(d.d_dialog), "user_lbl"));
    lfmt = gtk_label_get_text(label);
    if (optind < ac)
    {
	snprintf(lstr, sizeof(lstr), lfmt, av[optind]);
	++optind;
    }
    else
    {
	struct passwd	*pw;

	if ((pw = getpwuid(0)))
	    snprintf(lstr, sizeof(lstr), lfmt, pw->pw_name);
	else
	    snprintf(lstr, sizeof(lstr), lfmt, "root");
    }
    gtk_label_set_markup(label, lstr);

    /* Set the command label */
    label = GTK_LABEL(lookup_widget(GTK_WIDGET(d.d_dialog), "cmd_lbl"));
    lfmt = gtk_label_get_text(label);
    for (;
	    optind < ac && (*av[optind] == '-' || *av[optind] == '+');
	    ++optind)
	if (strchr(av[optind], 'c') && optind < ac - 1)
	{
	    register u_int  i;
	    char	    cmd[256 + 1];
	    size_t	    cl = 0;

	    ++optind;
	    for (i = optind; i < ac && cl < sizeof(cmd) - 1; ++i)
		cl+= snprintf(cmd + cl, sizeof(cmd), "%s%s",
			(cl ? " " : ""),
			av[i]);
	    snprintf(lstr, sizeof(lstr), lfmt, cmd);
	    break;
	}
    if (optind == ac)
	snprintf(lstr, sizeof(lstr), lfmt, _("(Shell)"));
    gtk_label_set_markup(label, lstr);

    /* Set the directory label */
    label = GTK_LABEL(lookup_widget(GTK_WIDGET(d.d_dialog), "dir_lbl"));
    lfmt = gtk_label_get_text(label);
    snprintf(lstr, sizeof(lstr), lfmt, getcwd(cwd, sizeof(cwd)));
    gtk_label_set_markup(label, lstr);

    switch ((d.d_ptypid = forkpty(&(d.d_ptyfd), NULL, NULL, NULL)))
    {
	struct termios	c;

	case 0:
	    av[0] = SU_CMD;
	    if (execv(av[0], av) == -1)
	    {
		fprintf(stderr, _("Could not exec %s: %s\n"),
			av[0],
			strerror(errno));
		return EX_UNAVAILABLE;
	    }

	case -1:
	    xmk_error(NULL,
		    EX_OSERR,
		    TRUE,
		    _("Could not create pseudo-terminal"));

	default:
	    if (ioctl(d.d_ptyfd, TIOCGETA, &c) == -1)
	    {
		warn("get terminfo on pty");
		d.d_eofch = '\4';   /* ^D */
	    }
	    else
		d.d_eofch = c.c_cc[VEOF];

	    gtk_input_add_full(d.d_ptyfd,
		    GDK_INPUT_READ,
		    (GdkInputFunction)_pty_input,
		    NULL,
		    &d,
		    NULL);
	    gtk_input_add_full(STDIN_FILENO,
		    GDK_INPUT_READ,
		    (GdkInputFunction)_tty_input,
		    NULL,
		    &d,
		    NULL);

	    gtk_main();
    }

    if (waitpid(d.d_ptypid, &st, 0) == -1)
	xmk_error(NULL, EX_OSERR, TRUE, _("Could not reap child"));

#ifdef DEBUG
    warnx("child status %d\n", st);
#endif /* DEBUG */

    if (WEXITSTATUS(st))
    {
	if (d.d_lastlen)
	    xmk_error(NULL, 1, FALSE, _("SU Failed:\n\n%s"), d.d_lastline);
	else
	    xmk_error(NULL,
		    WEXITSTATUS(st),
		    FALSE,
		    _("SU Failed: child exit status %d"),
		    WEXITSTATUS(st));
    }

    return xmk_gtk_exit();
}

