root/usr.bin/cu/command.c
/* $OpenBSD: command.c,v 1.18 2019/06/28 13:35:00 deraadt Exp $ */

/*
 * Copyright (c) 2012 Nicholas Marriott <nicm@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

#include <event.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <vis.h>

#include "cu.h"

void    pipe_command(void);
void    connect_command(void);
void    send_file(void);
void    send_xmodem(void);
void    set_speed(void);
void    start_record(void);

void
pipe_command(void)
{
        const char      *cmd;
        pid_t            pid;
        int              fd;

        cmd = get_input("Local command?");
        if (cmd == NULL || *cmd == '\0')
                return;

        restore_termios();
        set_blocking(line_fd, 1);

        switch (pid = fork()) {
        case -1:
                cu_err(1, "fork");
        case 0:
                fd = open(_PATH_DEVNULL, O_RDWR);
                if (fd == -1 || dup2(fd, STDIN_FILENO) == -1)
                        _exit(1);
                close(fd);

                if (signal(SIGINT, SIG_DFL) == SIG_ERR)
                        _exit(1);
                if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
                        _exit(1);

                /* attach stdout to line */
                if (dup2(line_fd, STDOUT_FILENO) == -1)
                        _exit(1);

                if (closefrom(STDERR_FILENO + 1) != 0)
                        _exit(1);

                execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL);
                _exit(1);
        default:
                while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
                        /* nothing */;
                break;
        }

        set_blocking(line_fd, 0);
        set_termios();
}

void
connect_command(void)
{
        const char      *cmd;
        pid_t            pid;

        /*
         * Fork a program with:
         *  0 <-> remote tty in
         *  1 <-> remote tty out
         *  2 <-> local tty stderr
         */

        cmd = get_input("Local command?");
        if (cmd == NULL || *cmd == '\0')
                return;

        restore_termios();
        set_blocking(line_fd, 1);

        switch (pid = fork()) {
        case -1:
                cu_err(1, "fork");
        case 0:
                if (signal(SIGINT, SIG_DFL) == SIG_ERR)
                        _exit(1);
                if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
                        _exit(1);

                /* attach stdout and stdin to line */
                if (dup2(line_fd, STDOUT_FILENO) == -1)
                        _exit(1);
                if (dup2(line_fd, STDIN_FILENO) == -1)
                        _exit(1);

                if (closefrom(STDERR_FILENO + 1) != 0)
                        _exit(1);

                execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL);
                _exit(1);
        default:
                while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
                        /* nothing */;
                break;
        }

        set_blocking(line_fd, 0);
        set_termios();
}

void
send_file(void)
{
        const char      *file;
        FILE            *f;
        char             buf[BUFSIZ], *expanded;
        size_t           len;

        file = get_input("Local file?");
        if (file == NULL || *file == '\0')
                return;

        expanded = tilde_expand(file);
        f = fopen(expanded, "r");
        if (f == NULL) {
                cu_warn("%s", file);
                return;
        }

        while (!feof(f) && !ferror(f)) {
                len = fread(buf, 1, sizeof(buf), f);
                if (len != 0)
                        bufferevent_write(line_ev, buf, len);
        }

        fclose(f);
        free(expanded);
}

void
send_xmodem(void)
{
        const char      *file;
        char            *expanded;

        file = get_input("Local file?");
        if (file == NULL || *file == '\0')
                return;

        expanded = tilde_expand(file);
        xmodem_send(expanded);
        free(expanded);
}

void
set_speed(void)
{
        const char      *s, *errstr;
        int              speed;

        s = get_input("New speed?");
        if (s == NULL || *s == '\0')
                return;

        speed = strtonum(s, 0, UINT_MAX, &errstr);
        if (errstr != NULL) {
                cu_warnx("speed is %s: %s", errstr, s);
                return;
        }

        if (set_line(speed) != 0)
                cu_warn("tcsetattr");
}

void
start_record(void)
{
        const char      *file;

        if (record_file != NULL) {
                fclose(record_file);
                record_file = NULL;
        }

        file = get_input("Record file?");
        if (file == NULL || *file == '\0')
                return;

        record_file = fopen(file, "a");
        if (record_file == NULL)
                cu_warnx("%s", file);
}

void
do_command(char c)
{
        char esc[4 + 1];

        if (restricted && strchr("CRX$>", c) != NULL) {
                cu_warnx("~%c command is not allowed in restricted mode", c);
                return;
        }

        switch (c) {
        case '.':
        case '\004': /* ^D */
                event_loopexit(NULL);
                break;
        case '\032': /* ^Z */
                restore_termios();
                kill(getpid(), SIGTSTP);
                set_termios();
                break;
        case 'C':
                connect_command();
                break;
        case 'D':
                ioctl(line_fd, TIOCCDTR, NULL);
                sleep(1);
                ioctl(line_fd, TIOCSDTR, NULL);
                break;
        case 'R':
                start_record();
                break;
        case 'S':
                set_speed();
                break;
        case 'X':
                send_xmodem();
                break;
        case '$':
                pipe_command();
                break;
        case '>':
                send_file();
                break;
        case '#':
                ioctl(line_fd, TIOCSBRK, NULL);
                sleep(1);
                ioctl(line_fd, TIOCCBRK, NULL);
                break;
        default:
                if ((u_char)c == escape_char)
                        bufferevent_write(line_ev, &c, 1);
                break;
        case '?':
                vis(esc, escape_char, VIS_WHITE | VIS_NOSLASH, 0);
                printf("\r\n"
                    "%s#      send break\r\n"
                    "%s$      pipe local command to remote host\r\n"
                    "%s>      send file to remote host\r\n"
                    "%sC      connect program to remote host\r\n"
                    "%sD      de-assert DTR line briefly\r\n"
                    "%sR      start recording to file\r\n"
                    "%sS      set speed\r\n"
                    "%sX      send file with XMODEM\r\n"
                    "%s?      get this summary\r\n",
                    esc, esc, esc, esc, esc, esc, esc, esc, esc
                );
                break;
        }
}