/*
20140120
Jan Mojzis
Public domain.
*/

#include "buf.h"
#include "ssh.h"
#include "e.h"
#include "bug.h"
#include "str.h"
#include "log.h"
#include "packetparser.h"
#include "packet.h"
#include "channel.h"

static int stringhaszero(const char *s, crypto_uint32 len) {
    crypto_uint32 i;

    for (i = 0; i < len; ++i)
        if (!s[i]) return 1;
    return 0;
}

int packet_channel_request(struct buf *b1, struct buf *b2,
                           const char *customcmd) {

    long long pos = 0;
    crypto_uint8 ch, wantreply;
    crypto_uint32 id, a, b, x, y;
    char *cmd;
    crypto_uint32 cmdlen;
    char *p1, *p2;
    const char *ps;
    crypto_uint32 plen1, plen2;

    pos = packetparser_uint8(b1->buf, b1->len, pos,
                             &ch); /* byte      SSH_MSG_CHANNEL_REQUEST */
    if (ch != SSH_MSG_CHANNEL_REQUEST) bug_proto();
    pos = packetparser_uint32(b1->buf, b1->len, pos,
                              &id); /* uint32    recipient channel */
    if (id != channel_getid()) bug_proto();
    pos = packetparser_uint32(
        b1->buf, b1->len, pos,
        &cmdlen); /* string    request type in US-ASCII characters only */
    cmd = (char *) b1->buf + pos;
    pos = packetparser_skip(b1->buf, b1->len, pos, cmdlen);
    pos = packetparser_uint8(b1->buf, b1->len, pos,
                             &wantreply); /* boolean   want reply */
    cmd[cmdlen] = 0;

    if (str_equaln(cmd, cmdlen, "exec")) {

        /*      byte      SSH_MSG_CHANNEL_REQUEST
                uint32    recipient channel
                string    "exec"
                boolean   want reply
                string    command
        */
        pos = packetparser_uint32(b1->buf, b1->len, pos, &plen1);
        p1 = (char *) b1->buf + pos;
        pos = packetparser_skip(b1->buf, b1->len, pos, plen1);
        pos = packetparser_end(b1->buf, b1->len, pos);
        buf_putnum8(b1, 0);
        if (stringhaszero(p1, plen1)) goto reject;
        p1[plen1] = 0;

        if (channel.pid != 0) {
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, exec ", p1,
                   ", rejected: session already started");
            goto reject;
        }

        if (customcmd) {
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, exec ", p1,
                   ", rejected: request disabled by -e");
            goto reject;
        }

        if (!channel_exec(p1)) bug();
        log_d3("packet=SSH_MSG_CHANNEL_REQUEST, exec ", p1, ", accepted");
        goto accept;
    }

    if (str_equaln(cmd, cmdlen, "subsystem")) {

        /*      byte      SSH_MSG_CHANNEL_REQUEST
                uint32    recipient channel
                string    "subsystem"
                boolean   want reply
                string    subsystem name
        */
        pos = packetparser_uint32(b1->buf, b1->len, pos, &plen1);
        p1 = (char *) b1->buf + pos;
        pos = packetparser_skip(b1->buf, b1->len, pos, plen1);
        pos = packetparser_end(b1->buf, b1->len, pos);
        buf_putnum8(b1, 0);
        if (stringhaszero(p1, plen1)) goto reject;
        p1[plen1] = 0;

        if (channel.pid != 0) {
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, subsystem ", p1,
                   ", rejected: session already started");
            goto reject;
        }

        if (customcmd) {
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, subsystem ", p1,
                   ", rejected: request disabled by -e");
            goto reject;
        }

        ps = channel_subsystem_get(p1);
        if (!ps) {
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, subsystem ", p1,
                   ", rejected");
            goto reject;
        }

        if (!channel_exec(ps)) bug();
        log_d5("packet=SSH_MSG_CHANNEL_REQUEST, subsystem ", p1, "=", ps,
               ", accepted");

        goto accept;
    }

    if (str_equaln(cmd, cmdlen, "shell")) {

        /*      byte      SSH_MSG_CHANNEL_REQUEST
                uint32    recipient channel
                string    "shell"
                boolean   want reply
        */

        pos = packetparser_end(b1->buf, b1->len, pos);

        if (channel.pid != 0) {
            log_d1("packet=SSH_MSG_CHANNEL_REQUEST, shell, rejected: session "
                   "already started");
            goto reject;
        }

        if (customcmd) {
            if (!channel_exec(customcmd)) bug();
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, shell, accepted, executing "
                   "custom shell '",
                   customcmd, "'");
        }
        else {
            if (!channel_exec(0)) bug();
            log_d1("packet=SSH_MSG_CHANNEL_REQUEST, shell, accepted");
        }
        goto accept;
    }

    if (str_equaln(cmd, cmdlen, "env")) {

        /*      byte      SSH_MSG_CHANNEL_REQUEST
                uint32    recipient channel
                string    "env"
                boolean   want reply
                string    variable name
                string    variable value
        */
        /**/
        pos = packetparser_uint32(b1->buf, b1->len, pos,
                                  &plen1); /* string    variable name */
        p1 = (char *) b1->buf + pos;
        pos = packetparser_skip(b1->buf, b1->len, pos, plen1);
        pos = packetparser_uint32(b1->buf, b1->len, pos,
                                  &plen2); /* string    variable value */
        p2 = (char *) b1->buf + pos;
        pos = packetparser_skip(b1->buf, b1->len, pos, plen2);
        pos = packetparser_end(b1->buf, b1->len, pos);
        buf_putnum8(b1, 0);
        if (stringhaszero(p1, plen1) || stringhaszero(p2, plen2)) goto reject;
        p1[plen1] = 0;
        p2[plen2] = 0;

        log_d5("packet=SSH_MSG_CHANNEL_REQUEST, env ", p1, "=", p2,
               ", rejected: environment variables disabled for security "
               "reasons");
        goto reject;
    }

    if (str_equaln(cmd, cmdlen, "pty-req")) {
        /*
                byte      SSH_MSG_CHANNEL_REQUEST
                uint32    recipient channel
                string    "pty-req"
                boolean   want_reply
                string    TERM environment variable value (e.g., vt100)
                uint32    terminal width, characters (e.g., 80)
                uint32    terminal height, rows (e.g., 24)
                uint32    terminal width, pixels (e.g., 640)
                uint32    terminal height, pixels (e.g., 480)
                string    encoded terminal modes
        */
        pos = packetparser_uint32(b1->buf, b1->len, pos, &plen1);
        p1 = (char *) b1->buf + pos;
        pos = packetparser_skip(b1->buf, b1->len, pos, plen1);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &a);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &b);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &x);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &y);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &plen2);
        p2 = (char *) b1->buf + pos;
        pos = packetparser_skip(b1->buf, b1->len, pos, plen2);
        pos = packetparser_end(b1->buf, b1->len, pos);
        buf_putnum8(b1, 0);
        /* XXX TODO encoded terminal modes (p2, plen2) */
        if (stringhaszero(p1, plen1)) goto reject;
        p1[plen1] = 0;
        p2[plen2] = 0;
        if (channel.pid != 0 || channel.flagterminal) {
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, pty-req ", p1,
                   ", rejected: terminal already initialized");
            goto reject;
        }

        if (!channel_openterminal(p1, a, b, x, y)) {
            log_w1("unable to open terminal");
            log_d3("packet=SSH_MSG_CHANNEL_REQUEST, pty-req ", p1,
                   ", rejected");
            goto reject;
        }
        log_d3("packet=SSH_MSG_CHANNEL_REQUEST, pty-req ", p1, ", accepted");
        goto accept;
    }

    if (str_equaln(cmd, cmdlen, "window-change")) {
        /*
              byte      SSH_MSG_CHANNEL_REQUEST
              uint32    recipient channel
              string    "window-change"
              boolean   FALSE
              uint32    terminal width, columns
              uint32    terminal height, rows
              uint32    terminal width, pixels
              uint32    terminal height, pixels
        */
        pos = packetparser_uint32(b1->buf, b1->len, pos, &a);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &b);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &x);
        pos = packetparser_uint32(b1->buf, b1->len, pos, &y);
        pos = packetparser_end(b1->buf, b1->len, pos);

        if (channel.pid <= 0 || !channel.flagterminal) {
            log_d1("packet=SSH_MSG_CHANNEL_REQUEST, window-change, rejected: "
                   "no active terminal");
            goto reject;
        }

        channel_ptyresize(a, b, x, y);
        log_d1("packet=SSH_MSG_CHANNEL_REQUEST, window-change, accepted");
        goto accept;
    }

    log_d3("packet=SSH_MSG_CHANNEL_REQUEST, ", cmd, ", rejected or ignored");

reject:
    /* reject channel request */
    if (!wantreply) goto done;
    buf_purge(b2);
    buf_putnum8(b2, SSH_MSG_CHANNEL_FAILURE); /* byte SSH_MSG_CHANNEL_SUCCESS */
    buf_putnum32(b2, id);                     /* uint32    recipient channel */
    packet_put(b2);
    buf_purge(b1);
    buf_purge(b2);
    return 1;

accept:
    if (!wantreply) goto done;
    buf_purge(b2);
    buf_putnum8(b2, SSH_MSG_CHANNEL_SUCCESS); /* byte SSH_MSG_CHANNEL_SUCCESS */
    buf_putnum32(b2, id);                     /* uint32    recipient channel */
    packet_put(b2);
    buf_purge(b1);
    buf_purge(b2);
    return 1;

done:
    buf_purge(b1);
    buf_purge(b2);
    return 1;
}
