kylemanna
6/1/2013 - 12:29 PM

Program used to control ssh execution. Example usage: "./check-ip 192.168.0.0/16 /path/to/trusted/command arg1". Or could be used in an auth

Program used to control ssh execution. Example usage: "./check-ip 192.168.0.0/16 /path/to/trusted/command arg1". Or could be used in an authorized_keys file with SSH_ORIGINAL_COMMAND set.

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>

#ifndef CMD_DEFAULT
#define CMD_DEFAULT "/bin/bash"
#endif

/*@unused@*/
static inline int print_in6(const struct in6_addr *in)
{
    typedef uint8_t chunk;
    size_t cnt = sizeof(*in)/sizeof(chunk);
    size_t i;

    for (i = 0; i < cnt; i++)
        printf("%02x", (unsigned int)((chunk *)in)[i]);

    printf("\n");
    return 0;
}

static inline int check_subnet(unsigned int subnet_mask, const struct in6_addr *net_raw, const struct in6_addr *addr)
{
    typedef uint8_t chunk;
    struct in6_addr bitmask;
    struct in6_addr and;
    struct in6_addr net_cooked;
    unsigned int i;

    assert(net_raw != NULL);
    assert(addr != NULL);
    assert(subnet_mask <= 128);

    memset(&bitmask, 0, sizeof(bitmask));
    memset(&and, 0, sizeof(and));
    memset(&net_cooked, 0, sizeof(net_cooked));

    /* Assemble the bitmask for IPv6 address */
    for (i = 0; i < subnet_mask; i++) {
        div_t a = div((int)i, (int)(sizeof(chunk) * 8));
        uint8_t s = (uint8_t)(7 - a.rem);
        ((chunk *)&bitmask)[a.quot] |= 1 << s;
    }

    /* net_cooked.s_addr = net_raw->s_addr & bitmask.s_addr; */
    for (i = 0; i < (unsigned int)(sizeof(net_cooked)/sizeof(chunk)); i++) {
        ((chunk *)&net_cooked)[i] = ((chunk *)net_raw)[i] & ((chunk *)&bitmask)[i];
    }

    /* and.s_addr = net_cooked.s_addr & addr->s_addr; */
    for (i = 0; i < (unsigned int)(sizeof(and)/sizeof(chunk)); i++) {
        ((chunk *)&and)[i] = ((chunk *)&bitmask)[i] & ((chunk *)addr)[i];
    }

#ifdef DEBUG
    printf("bitmask = ");
    print_in6(&bitmask);
    printf("net_raw = ");
    print_in6(net_raw);
    printf("net_cooked = ");
    print_in6(&net_cooked);
    printf("addr = ");
    print_in6(addr);
    printf("and = ");
    print_in6(&and);
#endif

    for (i = 0; i < (unsigned int)(sizeof(net_cooked)/sizeof(chunk)); i++) {
        if (((chunk *)&net_cooked)[i] != ((chunk *)&and)[i])
            return 1;
    }
    return 0;
}

/* Returns 1 if converted, 0 if not converted, < 0 on error */
static inline int ipv4_to_ipv6(char *addr_str, size_t addr_str_len) {
    int ret;
    char src[INET6_ADDRSTRLEN];
    /* const char prefix[] = "::ffff:"; */

    assert(addr_str != NULL);

    if (addr_str_len < INET6_ADDRSTRLEN)
        return -1;

    strncpy(src, addr_str, sizeof(src));

    ret = snprintf(addr_str, addr_str_len, "::ffff:%s", src);
    assert(ret <= INET6_ADDRSTRLEN);

    return 0;
}

static inline int inet_ptoipv6(char *addr_str, size_t addr_str_len, struct in6_addr *addr, int convert)
{
    int ret;
    ret = inet_pton(AF_INET6, addr_str, addr);
    if (ret == 0 && convert != 0) {

        ret = ipv4_to_ipv6(addr_str, addr_str_len);
        if (ret != 0)
            return ret;

        ret = inet_ptoipv6(addr_str, addr_str_len, addr, 0);
        if (ret == 1)
            ret = 2;

    } else if (ret == 0) {
        fprintf(stderr, "Not in presentation format\n");
    } else if (ret < 0) {
        perror("inet_pton");
    }
    return ret;
}

int main(int argc, char *argv[])
{
    char buf[64] = "::ffff:192.168.50.163 58033 ::ffff:192.168.50.128 22222";
    int ret;
    unsigned int subnet_mask;
    char addr_str[INET6_ADDRSTRLEN];
    char network_str[INET6_ADDRSTRLEN];
    struct in6_addr addr;
    struct in6_addr network;
    char *subnet;
    const char *cmd_default = CMD_DEFAULT;
    char *env;
    size_t len;

    if (argc < 2)
        exit(EXIT_FAILURE);

    /* ::ffff:192.168.50.163 58033 ::ffff:192.168.50.128 22222 */
    env = getenv("SSH_CONNECTION");
    if (env == NULL)
        exit(EXIT_FAILURE);

    strncpy(buf, env, sizeof(buf));
    if (buf[0] == '\0')
        exit(EXIT_FAILURE);

    /* Grab only the host address */
    len = strchr(buf, ' ') - buf;
    buf[len] = '\0';
    strncpy(addr_str, buf, sizeof(addr_str));

    ret = inet_ptoipv6(addr_str, sizeof(addr_str), &addr, 1);
    if (ret < 1) {
        exit(EXIT_FAILURE);
    }

    /* Split up subnet mask from the network address */
    subnet = strchr(argv[1], '/');
    if (subnet == NULL) {
        exit(EXIT_FAILURE);
    }
    subnet_mask = (unsigned int)atoi(subnet + 1);
    if (subnet_mask > 128) {
        exit(EXIT_FAILURE);
    }
    len = (size_t)(subnet - argv[1]);
    memcpy(network_str, argv[1], len);
    network_str[len] = '\0';

    ret = inet_ptoipv6(network_str, sizeof(network_str), &network, 1);
    if (ret == 2) {
        subnet_mask += (sizeof(struct in6_addr) - sizeof(struct in_addr)) * 8;
    } else if (ret < 1) {
        exit(EXIT_FAILURE);
    }

    ret = check_subnet(subnet_mask, &network, &addr);
    if (ret != 0) {
        exit(EXIT_FAILURE);
    }

    /* Connection is valid, attempt to execute requested command */

    env = getenv("SSH_ORIGINAL_COMMAND");
    if (env != NULL && env[0] != '\0') {

        system(env);

    } else {

        /* Variable length arrays are frowned upon, so malloc instead */
        char **args = malloc(sizeof(char *) * 2);
        if (args == NULL)
            exit(EXIT_FAILURE);

        /* Default arguments */
        args[0] = (char *)cmd_default;
        args[1] = NULL;

        /* Copy the arguments pointer if given */
        if (argc > 2) {
            int i, j;
            int arg_len = argc - 2 + 1;

            args = (char **)realloc(args, sizeof(char *) * arg_len);
            args[arg_len - 1] = NULL;

            for (i = 0, j = 2; i < arg_len; i++, j++)
                args[i] = argv[j];

        }
        ret = execvp(args[0], (char *const *)args);

        /* The above function will never return unless an error happens */

        free(args);
    }

    return 0;
}