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;
}