Merge branch 'auth'

master
Nicolas Favre-Felix 14 years ago
commit 78e64f7d45

@ -2,7 +2,7 @@ OUT=webdis
HIREDIS_OBJ=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o
JANSSON_OBJ=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o jansson/src/load.o jansson/src/strbuffer.o jansson/src/utf.o jansson/src/value.o jansson/src/variadic.o
FORMAT_OBJS=formats/json.o formats/raw.o
OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ)
OBJS=webdis.o conf.o $(FORMAT_OBJS) cmd.o server.o $(HIREDIS_OBJ) $(JANSSON_OBJ) libb64/cencode.o
CFLAGS=-O3 -Wall -Wextra -I. -Ijansson/src
LDFLAGS=-levent

36
cmd.c

@ -36,45 +36,53 @@ cmd_free(struct cmd *c) {
}
int
cmd_authorized(struct conf *cfg, struct evhttp_request *rq, const char *verb, size_t verb_len) {
cmd_authorized(struct cmd *cmd, struct conf *cfg, struct evhttp_request *rq) {
char *always_off[] = {"MULTI", "EXEC", "WATCH", "DISCARD", "SUBSCRIBE", "PSUBSCRIBE"};
struct disabled_command *dc;
unsigned int i;
int authorized = 1;
struct acl *a;
char *client_ip;
u_short client_port;
in_addr_t client_addr;
const char *cmd_name = cmd->argv[0];
size_t cmd_len = cmd->argv_len[0];
/* some commands are always disabled, regardless of the config file. */
for(i = 0; i < sizeof(always_off) / sizeof(always_off[0]); ++i) {
if(strncasecmp(always_off[i], verb, verb_len) == 0) {
if(strncasecmp(always_off[i], cmd_name, cmd_len) == 0) {
return 0;
}
}
/* find client's address */
evhttp_connection_get_peer(rq->evcon, &client_ip, &client_port);
client_addr = ntohl(inet_addr(client_ip));
for(dc = cfg->disabled; dc; dc = dc->next) {
/* CIDR test */
/* go through permissions */
for(a = cfg->perms; a; a = a->next) {
if((client_addr & dc->mask) != (dc->subnet & dc->mask)) {
continue;
if(!acl_match(a, rq, &client_addr)) continue; /* match client */
/* go through authorized commands */
for(i = 0; i < a->enabled.count; ++i) {
if(strncasecmp(a->enabled.commands[i], cmd_name, cmd_len) == 0) {
authorized = 1;
}
}
/* matched an ip */
for(i = 0; i < dc->count; ++i) {
if(strncasecmp(dc->commands[i], verb, verb_len) == 0) {
return 0;
/* go through unauthorized commands */
for(i = 0; i < a->disabled.count; ++i) {
if(strncasecmp(a->disabled.commands[i], cmd_name, cmd_len) == 0) {
authorized = 0;
}
}
}
return 1;
return authorized;
}
int
@ -117,7 +125,7 @@ cmd_run(struct server *s, struct evhttp_request *rq,
cmd->argv_len[0] = cmd_len;
/* check that the client is able to run this command */
if(!cmd_authorized(s->cfg, rq, cmd->argv[0], cmd->argv_len[0])) {
if(!cmd_authorized(cmd, s->cfg, rq)) {
return -1;
}

177
conf.c

@ -5,10 +5,12 @@
#include <arpa/inet.h>
#include <jansson.h>
#include <evhttp.h>
#include <libb64/cencode.h>
#include "conf.h"
static struct disabled_command *
conf_disable_commands(json_t *jtab);
static struct acl *
conf_parse_acls(json_t *jtab);
struct conf *
conf_read(const char *filename) {
@ -46,8 +48,8 @@ conf_read(const char *filename) {
conf->http_host = strdup(json_string_value(jtmp));
} else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
conf->http_port = (short)json_integer_value(jtmp);
} else if(strcmp(json_object_iter_key(kv), "disable") == 0 && json_typeof(jtmp) == JSON_OBJECT) {
conf->disabled = conf_disable_commands(jtmp);
} else if(strcmp(json_object_iter_key(kv), "acl") == 0 && json_typeof(jtmp) == JSON_ARRAY) {
conf->perms = conf_parse_acls(jtmp);
}
}
@ -56,30 +58,52 @@ conf_read(const char *filename) {
return conf;
}
void
acl_read_commands(json_t *jlist, struct acl_commands *ac) {
struct disabled_command *
conf_disable_commands(json_t *jtab) {
unsigned int i, n, cur;
struct disabled_command *root = NULL;
/* count strings in the array */
for(i = 0, n = 0; i < json_array_size(jlist); ++i) {
json_t *jelem = json_array_get(jlist, (size_t)i);
if(json_typeof(jelem) == JSON_STRING) {
n++;
}
}
void *kv;
for(kv = json_object_iter(jtab); kv; kv = json_object_iter_next(jtab, kv)) {
/* allocate block */
ac->commands = calloc((size_t)n, sizeof(char*));
ac->count = n;
/* add all disabled commands */
for(i = 0, cur = 0; i < json_array_size(jlist); ++i) {
json_t *jelem = json_array_get(jlist, i);
if(json_typeof(jelem) == JSON_STRING) {
size_t sz;
const char *s = json_string_value(jelem);
sz = strlen(s);
ac->commands[cur] = calloc(1 + sz, 1);
memcpy(ac->commands[cur], s, sz);
cur++;
}
}
}
unsigned int i, cur, n;
char *p, *ip;
const char *s;
in_addr_t mask, subnet;
unsigned short mask_bits = 0;
struct acl *
conf_parse_acl(json_t *j) {
struct disabled_command *dc;
json_t *val = json_object_iter_value(kv);
json_t *jcidr, *jbasic, *jlist;
unsigned short mask_bits = 0;
if(json_typeof(val) != JSON_ARRAY) {
continue; /* TODO: report error? */
}
struct acl *a = calloc(1, sizeof(struct acl));
/* parse key in format "ip/mask" */
s = json_object_iter_key(kv);
/* parse CIDR */
if((jcidr = json_object_get(j, "ip")) && json_typeof(jcidr) == JSON_STRING) {
const char *s;
char *p, *ip;
s = json_string_value(jcidr);
p = strchr(s, '/');
if(!p) {
ip = strdup(s);
@ -88,45 +112,92 @@ conf_disable_commands(json_t *jtab) {
memcpy(ip, s, (size_t)(p - s));
mask_bits = (unsigned short)atoi(p+1);
}
mask = (mask_bits == 0 ? 0 : (0xffffffff << (32 - mask_bits)));
subnet = ntohl(inet_addr(ip)) & mask;
/* count strings in the array */
n = 0;
for(i = 0; i < json_array_size(val); ++i) {
json_t *jelem = json_array_get(val, (size_t)i);
if(json_typeof(jelem) == JSON_STRING) {
n++;
}
a->cidr.enabled = 1;
a->cidr.mask = (mask_bits == 0 ? 0 : (0xffffffff << (32 - mask_bits)));
a->cidr.subnet = ntohl(inet_addr(ip)) & a->cidr.mask;
free(ip);
}
/* parse basic_auth */
if((jbasic = json_object_get(j, "http_basic_auth")) && json_typeof(jbasic) == JSON_STRING) {
/* base64 encode */
base64_encodestate b64;
int pos;
char *p;
const char *plain = json_string_value(jbasic);
size_t len, plain_len = strlen(plain) + 0;
len = (plain_len + 8) * 8 / 6;
a->http_basic_auth = calloc(len, 1);
base64_init_encodestate(&b64);
pos = base64_encode_block(plain, (int)plain_len, a->http_basic_auth, &b64); /* FIXME: check return value */
base64_encode_blockend(a->http_basic_auth + pos, &b64);
/* end string with \0 rather than \n */
if((p = strchr(a->http_basic_auth + pos, '\n'))) {
*p = 0;
}
}
/* parse enabled commands */
if((jlist = json_object_get(j, "enabled")) && json_typeof(jlist) == JSON_ARRAY) {
acl_read_commands(jlist, &a->enabled);
}
/* parse disabled commands */
if((jlist = json_object_get(j, "disabled")) && json_typeof(jlist) == JSON_ARRAY) {
acl_read_commands(jlist, &a->disabled);
}
return a;
}
struct acl *
conf_parse_acls(json_t *jtab) {
struct acl *head = NULL, *tail = NULL, *tmp;
unsigned int i;
for(i = 0; i < json_array_size(jtab); ++i) {
json_t *val = json_array_get(jtab, i);
tmp = conf_parse_acl(val);
if(head == NULL && tail == NULL) {
head = tail = tmp;
} else {
tail->next = tmp;
tail = tmp;
}
}
return head;
}
int
acl_match(struct acl *a, struct evhttp_request *rq, in_addr_t *ip) {
/* allocate block */
dc = calloc(1, sizeof(struct disabled_command));
dc->commands = calloc((size_t)n, sizeof(char*));
dc->subnet = subnet;
dc->mask = mask;
dc->count = n;
dc->next = root;
root = dc;
/* add all disabled commands */
for(i = 0, cur = 0; i < json_array_size(val); ++i) {
json_t *jelem = json_array_get(val, i);
if(json_typeof(jelem) == JSON_STRING) {
size_t sz;
s = json_string_value(jelem);
sz = strlen(s);
dc->commands[cur] = calloc(1 + sz, 1);
memcpy(dc->commands[cur], s, sz);
cur++;
}
/* check HTTP Basic Auth */
const char *auth;
auth = evhttp_find_header(rq->input_headers, "Authorization");
if(auth && a->http_basic_auth && strncasecmp(auth, "Basic ", 6) == 0) { /* sent auth */
if(strcmp(auth + 6, a->http_basic_auth) != 0) { /* wrong */
return 0;
}
}
return root;
/* CIDR check. */
if(a->cidr.enabled == 0) { /* none given, all match */
return 1;
}
if(((*ip) & a->cidr.mask) == (a->cidr.subnet & a->cidr.mask)) {
return 1;
}
return 0;
}
void
conf_free(struct conf *conf) {

@ -3,15 +3,29 @@
#include <netinet/in.h>
struct disabled_command {
in_addr_t subnet;
in_addr_t mask;
struct evhttp_request;
struct acl_commands {
unsigned int count;
char **commands;
};
struct acl {
/* CIDR subnet + mask */
struct {
int enabled;
in_addr_t subnet;
in_addr_t mask;
} cidr;
struct disabled_command *next;
char *http_basic_auth;
/* commands that have been enabled or disabled */
struct acl_commands enabled;
struct acl_commands disabled;
struct acl *next;
};
struct conf {
@ -23,7 +37,7 @@ struct conf {
char *http_host;
short http_port;
struct disabled_command *disabled;
struct acl *perms;
};
struct conf *
@ -32,4 +46,7 @@ conf_read(const char *filename);
void
conf_free(struct conf *conf);
int
acl_match(struct acl *a, struct evhttp_request *rq, in_addr_t *ip);
#endif /* CONF_H */

@ -0,0 +1,109 @@
/*
cencoder.c - c source to a base64 encoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cencode.h"
const int CHARS_PER_LINE = 72;
void base64_init_encodestate(base64_encodestate* state_in)
{
state_in->step = step_A;
state_in->result = 0;
state_in->stepcount = 0;
}
char base64_encode_value(char value_in)
{
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (value_in > 63) return '=';
return encoding[(int)value_in];
}
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
const char* plainchar = plaintext_in;
const char* const plaintextend = plaintext_in + length_in;
char* codechar = code_out;
char result;
char fragment;
result = state_in->result;
switch (state_in->step)
{
while (1)
{
case step_A:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_A;
return codechar - code_out;
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x003) << 4;
case step_B:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_B;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x00f) << 2;
case step_C:
if (plainchar == plaintextend)
{
state_in->result = result;
state_in->step = step_C;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x03f) >> 0;
*codechar++ = base64_encode_value(result);
++(state_in->stepcount);
if (state_in->stepcount == CHARS_PER_LINE/4)
{
*codechar++ = '\n';
state_in->stepcount = 0;
}
}
}
/* control should not reach here */
return codechar - code_out;
}
int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
char* codechar = code_out;
switch (state_in->step)
{
case step_B:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
*codechar++ = '=';
break;
case step_C:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
break;
case step_A:
break;
}
*codechar++ = '\n';
return codechar - code_out;
}

@ -0,0 +1,31 @@
/*
cencode.h - c header for a base64 encoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CENCODE_H
#define BASE64_CENCODE_H
typedef enum
{
step_A, step_B, step_C
} base64_encodestep;
typedef struct
{
base64_encodestep step;
char result;
int stepcount;
} base64_encodestate;
void base64_init_encodestate(base64_encodestate* state_in);
char base64_encode_value(char value_in);
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
#endif /* BASE64_CENCODE_H */

@ -1,12 +1,37 @@
{
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_auth": null,
"http_host": "0.0.0.0",
"http_port": 7379,
"disable": {
"0.0.0.0/0": ["DEBUG", "FLUSHDB", "FLUSHALL"]
}
"acl": [
{
"http_basic_auth": "user:password",
"disabled": ["DEBUG", "FLUSHDB", "FLUSHALL"],
"enabled": ["SET"]
},
{
"ip": "192.168.10.0/24",
"disabled": ["SET", "FLUSHDB", "FLUSHALL"],
"enabled": ["*"]
},
{
"http_basic_auth": "user:password",
"ip": "192.168.10.0/24",
"disabled": ["FLUSHDB", "FLUSHALL"],
"enabled": ["SET", "*"]
},
{
"ip": "0.0.0.0/0",
"disabled": ["SET"],
"enabled": ["SET"]
}
]
}

Loading…
Cancel
Save