diff --git a/Makefile b/Makefile index f368db3..5e2062f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS=-std=gnu99 -Wall -g -O0 .PHONY: all all: redsocks -obj = parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o +obj = parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o src = $(patsubst %.o,%.c,$(obj)) redsocks: $(obj) diff --git a/http-auth.c b/http-auth.c new file mode 100644 index 0000000..4cb1e56 --- /dev/null +++ b/http-auth.c @@ -0,0 +1,285 @@ +/* redsocks - transparent TCP-to-proxy redirector + * Copyright (C) 2007-2008 Leonid Evdokimov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * http-auth library, provide basic and digest scheme + * see RFC 2617 for details + * + */ + +#include +#include +#include +#include + +#include "utils.h" +#include "md5.h" +#include "base64.h" + +#include "http-auth.h" + +int strncmp_nocase(const char *a, const char *b, int num) +{ + if (!a || !b) { + if (!a && !b) + return 0; + return !a ? 1 : -1; + } + while (num --) { + if (!*a || !*b) { + if (!*a && !*b) + return 0; + return !*a ? 1 : -1; + } + if (tolower(*a) != tolower(*b)) + return (int)tolower(*a) - (int)tolower(*b); + a ++; + b ++; + } + return 0; +} + +char* basic_authentication_encode(const char *user, const char *passwd) +{ + /* prepare the user:pass key pair */ + int pair_len = strlen(user) + 1 + strlen(passwd); + char *pair_ptr = (char*)malloc(pair_len + 1); + + sprintf(pair_ptr, "%s:%s", user, passwd); + + /* calculate the final string length */ + int basic_len = 6 + BASE64_SIZE(pair_len); + char *basic_ptr = (char*)malloc(basic_len + 1); + + strcpy(basic_ptr, "Basic "); + + if (!base64_encode(basic_ptr + 6, basic_len - 6, (const uint8_t*)pair_ptr, pair_len)) + return NULL; + + return basic_ptr; +} + +#define MD5_HASHLEN (16) + +static void dump_hash(char *buf, const unsigned char *hash) +{ + int i; + + for (i = 0; i < MD5_HASHLEN; i++) { + buf += sprintf(buf, "%02x", hash[i]); + } + + *buf = 0; +} + +typedef struct { + const char *b, *e; +} param_token; + +/* Extract a parameter from the string (typically an HTTP header) at + **SOURCE and advance SOURCE to the next parameter. Return false + when there are no more parameters to extract. The name of the + parameter is returned in NAME, and the value in VALUE. If the + parameter has no value, the token's b==e.*/ + +static int extract_param(const char **source, param_token *name, param_token *value, char separator) +{ + const char *p = *source; + + while (isspace (*p)) + ++p; + + if (!*p) + { + *source = p; + return 0; /* no error; nothing more to extract */ + } + + /* Extract name. */ + name->b = p; + while (*p && !isspace (*p) && *p != '=' && *p != separator) + ++p; + name->e = p; + if (name->b == name->e) + return 0; /* empty name: error */ + + while (isspace (*p)) + ++p; + if (*p == separator || !*p) /* no value */ + { + value->b = value->e = ""; + if (*p == separator) ++p; + *source = p; + return 1; + } + if (*p != '=') + return 0; /* error */ + + /* *p is '=', extract value */ + ++p; + while (isspace (*p)) ++p; + if (*p == '"') /* quoted */ + { + value->b = ++p; + while (*p && *p != '"') ++p; + if (!*p) + return 0; + value->e = p++; + /* Currently at closing quote; find the end of param. */ + while (isspace (*p)) ++p; + while (*p && *p != separator) ++p; + if (*p == separator) + ++p; + else if (*p) + /* garbage after closed quote, e.g. foo="bar"baz */ + return 0; + } + else /* unquoted */ + { + value->b = p; + while (*p && *p != separator) ++p; + value->e = p; + while (value->e != value->b && isspace (value->e[-1])) + --value->e; + if (*p == separator) ++p; + } + *source = p; + return 1; + +} + +char* digest_authentication_encode(const char *line, const char *user, const char *passwd, const char *method, const char *path, int count, const char *cnonce) +{ + char *realm = NULL, *opaque = NULL, *nonce = NULL, *qop = NULL; + char nc[9]; + sprintf(nc, "%08x", count); + + const char *ptr = line; + param_token name, value; + + while (extract_param(&ptr, &name, &value, ',')) { + int namelen = name.e - name.b; + int valuelen = value.e - value.b; + + if (strncmp_nocase(name.b, "realm" , namelen) == 0) + strncpy(realm = (char *)malloc(valuelen + 1), value.b, valuelen); + if (strncmp_nocase(name.b, "opaque", namelen) == 0) + strncpy(opaque = (char *)malloc(valuelen + 1), value.b, valuelen); + if (strncmp_nocase(name.b, "nonce" , namelen) == 0) + strncpy(nonce = (char *)malloc(valuelen + 1), value.b, valuelen); + if (strncmp_nocase(name.b, "qop" , namelen) == 0) + strncpy(qop = (char *)malloc(valuelen + 1), value.b, valuelen); + } + + if (!realm || !nonce || !user || !passwd || !path || !method) { + free_null(realm); + free_null(opaque); + free_null(nonce); + free_null(qop); + return NULL; + } + + if (!qop && strncmp_nocase(qop, "auth", 4) != 0) { + /* FIXME: currently don't support auth-int */ + free_null(qop); + return NULL; + } + + /* calculate the digest value */ + md5_state_t ctx; + md5_byte_t hash[MD5_HASHLEN]; + char a1buf[MD5_HASHLEN * 2 + 1], a2buf[MD5_HASHLEN * 2 + 1]; + char response[MD5_HASHLEN * 2 + 1]; + + /* A1 = username-value ":" realm-value ":" passwd */ + md5_init(&ctx); + md5_append(&ctx, (md5_byte_t*)user, strlen(user)); + md5_append(&ctx, (md5_byte_t*)":", 1); + md5_append(&ctx, (md5_byte_t*)realm, strlen(realm)); + md5_append(&ctx, (md5_byte_t*)":", 1); + md5_append(&ctx, (md5_byte_t*)passwd, strlen(passwd)); + md5_finish(&ctx, hash); + dump_hash(a1buf, hash); + + /* A2 = Method ":" digest-uri-value */ + md5_init(&ctx); + md5_append(&ctx, (md5_byte_t*)method, strlen(method)); + md5_append(&ctx, (md5_byte_t*)":", 1); + md5_append(&ctx, (md5_byte_t*)path, strlen(path)); + md5_finish(&ctx, hash); + dump_hash(a2buf, hash); + + /* qop set: request-digest = H(A1) ":" nonce-value ":" nc-value ":" cnonce-value ":" qop-value ":" H(A2) */ + /* not set: request-digest = H(A1) ":" nonce-value ":" H(A2) */ + md5_init(&ctx); + md5_append(&ctx, (md5_byte_t*)a1buf, strlen(a1buf)); + md5_append(&ctx, (md5_byte_t*)":", 1); + md5_append(&ctx, (md5_byte_t*)nonce, strlen(nonce)); + md5_append(&ctx, (md5_byte_t*)":", 1); + if (qop) { + md5_append(&ctx, (md5_byte_t*)nc, strlen(nc)); + md5_append(&ctx, (md5_byte_t*)":", 1); + md5_append(&ctx, (md5_byte_t*)cnonce, strlen(cnonce)); + md5_append(&ctx, (md5_byte_t*)":", 1); + md5_append(&ctx, (md5_byte_t*)qop, strlen(qop)); + md5_append(&ctx, (md5_byte_t*)":", 1); + } + md5_append(&ctx, (md5_byte_t*)a2buf, strlen(a2buf)); + md5_finish(&ctx, hash); + dump_hash(response, hash); + + /* prepare the final string */ + int len = 256; + //if (!realm || !nonce || !user || !passwd || !path || !method) + len += strlen(user); + len += strlen(realm); + len += strlen(nonce); + len += strlen(path); + len += strlen(response); + + if (qop) { + len += strlen(qop); + len += strlen(nc); + len += strlen(cnonce); + } + + if (opaque) { + len += strlen(opaque); + } + + char *res = (char*)malloc(len); + if (!qop) { + sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", + user, realm, nonce, path, response); + } else { + sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", qop=%s, nc=%s, cnonce=\"%s\"", + user, realm, nonce, path, response, qop, nc, cnonce); + } + + if (opaque) { + char *p = res + strlen(res); + strcat (p, ", opaque=\""); + strcat (p, opaque); + strcat (p, "\""); + } + + free_null(realm); + free_null(opaque); + free_null(nonce); + free_null(qop); + return res; +} diff --git a/http-auth.h b/http-auth.h new file mode 100644 index 0000000..bf8cc94 --- /dev/null +++ b/http-auth.h @@ -0,0 +1,24 @@ +#ifndef HTTP_AUTH_H +#define HTTP_AUTH_H + + +int strncmp_nocase(const char *a, const char *b, int num); + + +/* + * Create the authentication header contents for the `Basic' scheme. + * This is done by encoding the string "USER:PASS" to base64 and + * prepending the string "Basic " in front of it. + * + */ + +char* basic_authentication_encode(const char *user, const char *passwd); + +/* + * Create the authentication header contents for the 'Digest' scheme. + * only md5 method is available, see RFC 2617 for detail. + * + */ +char* digest_authentication_encode(const char *line, const char *user, const char *passwd, const char *method, const char *path, int count, const char *cnonce); + +#endif /* HTTP_AUTH_H */ diff --git a/utils.h b/utils.h index b91b8ba..d4e2d42 100644 --- a/utils.h +++ b/utils.h @@ -20,6 +20,7 @@ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) +#define free_null(p) if (!(p)) ; else free (p) /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */