Commit d3577b78 authored by Sergey Lyubka's avatar Sergey Lyubka

Moved auth code to separate file

parent 199e0a33
...@@ -27,7 +27,7 @@ VERSION = $(shell perl -lne \ ...@@ -27,7 +27,7 @@ VERSION = $(shell perl -lne \
# The order in which files are listed is important # The order in which files are listed is important
SOURCES = src/internal.h src/string.c src/parse_date.c src/options.c \ SOURCES = src/internal.h src/string.c src/parse_date.c src/options.c \
src/mongoose.c src/crypto.c src/auth.c src/mongoose.c
TINY_SOURCES = ../mongoose.c main.c TINY_SOURCES = ../mongoose.c main.c
LUA_SOURCES = $(TINY_SOURCES) sqlite3.c lsqlite3.c lua_5.2.1.c LUA_SOURCES = $(TINY_SOURCES) sqlite3.c lsqlite3.c lua_5.2.1.c
......
#include "internal.h"
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
}
*to = '\0';
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx);
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
}
va_end(ap);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
qop == NULL || response == NULL) {
return 0;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
char name[PATH_MAX];
const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
struct file file = STRUCT_FILE_INITIALIZER;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
fp = mg_fopen(gpass, "r");
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
} else if (mg_stat(path, &file) && file.is_directory) {
mg_snprintf(name, sizeof(name), "%s%c%s",
path, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
} else {
// Try to find .htpasswd in requested directory.
for (p = path, e = p + strlen(p) - 1; e > p; e--)
if (e[0] == '/')
break;
mg_snprintf(name, sizeof(name), "%.*s%c%s",
(int) (e - p), p, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
}
return fp;
}
// Parsed Authorization header
struct ah {
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
};
// Return 1 on success. Always initializes the ah structure.
static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) {
char *name, *value, *s;
const char *auth_header;
(void) memset(ah, 0, sizeof(*ah));
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0;
}
// Make modifiable copy of the auth header
(void) mg_strlcpy(buf, auth_header + 7, buf_size);
s = buf;
// Parse authorization header
for (;;) {
// Gobble initial spaces
while (isspace(* (unsigned char *) s)) {
s++;
}
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
}
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
}
if (*name == '\0') {
break;
}
if (!strcmp(name, "username")) {
ah->user = value;
} else if (!strcmp(name, "cnonce")) {
ah->cnonce = value;
} else if (!strcmp(name, "response")) {
ah->response = value;
} else if (!strcmp(name, "uri")) {
ah->uri = value;
} else if (!strcmp(name, "qop")) {
ah->qop = value;
} else if (!strcmp(name, "nc")) {
ah->nc = value;
} else if (!strcmp(name, "nonce")) {
ah->nonce = value;
}
}
// CGI needs it as REMOTE_USER
if (ah->user != NULL) {
conn->request_info.remote_user = mg_strdup(ah->user);
} else {
return 0;
}
return 1;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct mg_connection *conn, FILE *fp) {
struct ah ah;
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
return 0;
}
// Loop over passwords file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
continue;
}
if (!strcmp(ah.user, f_user) &&
!strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
return check_password(conn->request_info.request_method, ha1, ah.uri,
ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
}
return 0;
}
// Return 1 if request is authorised, 0 otherwise.
static int check_authorization(struct mg_connection *conn, const char *path) {
char fname[PATH_MAX];
struct vec uri_vec, filename_vec;
const char *list;
FILE *fp = NULL;
int authorized = 1;
list = conn->ctx->config[PROTECT_URI];
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
mg_snprintf(fname, sizeof(fname), "%.*s",
(int) filename_vec.len, filename_vec.ptr);
fp = mg_fopen(fname, "r");
break;
}
}
if (fp == NULL) {
fp = open_auth_file(conn, path);
}
if (fp != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static void send_authorization_request(struct mg_connection *conn) {
conn->status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL));
}
static int is_authorized_for_put(struct mg_connection *conn) {
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
FILE *fp;
int ret = 0;
if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
ret = authorize(conn, fp);
fclose(fp);
}
return ret;
}
int mg_modify_passwords_file(const char *fname, const char *domain,
const char *user, const char *pass) {
int found;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
FILE *fp, *fp2;
found = 0;
fp = fp2 = NULL;
// Regard empty password as no password - remove user record.
if (pass != NULL && pass[0] == '\0') {
pass = NULL;
}
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist
if ((fp = fopen(fname, "a+")) != NULL) {
fclose(fp);
}
// Open the given file and temporary file
if ((fp = fopen(fname, "r")) == NULL) {
return 0;
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp);
return 0;
}
// Copy the stuff to temporary file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
continue;
}
if (!strcmp(u, user) && !strcmp(d, domain)) {
found++;
if (pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
} else {
fprintf(fp2, "%s", line);
}
}
// If new user, just add it
if (!found && pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
// Close files
fclose(fp);
fclose(fp2);
// Put the temp file in place of real file
remove(fname);
rename(tmp, fname);
return 1;
}
static int is_big_endian(void) {
static const int n = 1;
return ((char *) &n)[0] == 0;
}
#ifndef HAVE_MD5
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
}
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
}
memcpy(ctx->in, buf, len);
}
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
} else {
memset(p, 0, count - 8);
}
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5
...@@ -458,3 +458,5 @@ struct de { ...@@ -458,3 +458,5 @@ struct de {
struct file file; struct file file;
}; };
static FILE *mg_fopen(const char *path, const char *mode);
static int mg_stat(const char *path, struct file *filep);
...@@ -1205,505 +1205,6 @@ static void get_mime_type(struct mg_context *ctx, const char *path, ...@@ -1205,505 +1205,6 @@ static void get_mime_type(struct mg_context *ctx, const char *path,
vec->len = strlen(vec->ptr); vec->len = strlen(vec->ptr);
} }
static int is_big_endian(void) {
static const int n = 1;
return ((char *) &n)[0] == 0;
}
#ifndef HAVE_MD5
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
}
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
}
memcpy(ctx->in, buf, len);
}
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
} else {
memset(p, 0, count - 8);
}
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
}
*to = '\0';
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx);
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
}
va_end(ap);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
qop == NULL || response == NULL) {
return 0;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
char name[PATH_MAX];
const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
struct file file = STRUCT_FILE_INITIALIZER;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
if ((fp = mg_fopen(gpass, "r")) == NULL) {
cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO));
}
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
} else if (mg_stat(path, &file) && file.is_directory) {
mg_snprintf(name, sizeof(name), "%s%c%s",
path, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
} else {
// Try to find .htpasswd in requested directory.
for (p = path, e = p + strlen(p) - 1; e > p; e--)
if (e[0] == '/')
break;
mg_snprintf(name, sizeof(name), "%.*s%c%s",
(int) (e - p), p, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
}
return fp;
}
// Parsed Authorization header
struct ah {
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
};
// Return 1 on success. Always initializes the ah structure.
static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) {
char *name, *value, *s;
const char *auth_header;
(void) memset(ah, 0, sizeof(*ah));
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0;
}
// Make modifiable copy of the auth header
(void) mg_strlcpy(buf, auth_header + 7, buf_size);
s = buf;
// Parse authorization header
for (;;) {
// Gobble initial spaces
while (isspace(* (unsigned char *) s)) {
s++;
}
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
}
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
}
if (*name == '\0') {
break;
}
if (!strcmp(name, "username")) {
ah->user = value;
} else if (!strcmp(name, "cnonce")) {
ah->cnonce = value;
} else if (!strcmp(name, "response")) {
ah->response = value;
} else if (!strcmp(name, "uri")) {
ah->uri = value;
} else if (!strcmp(name, "qop")) {
ah->qop = value;
} else if (!strcmp(name, "nc")) {
ah->nc = value;
} else if (!strcmp(name, "nonce")) {
ah->nonce = value;
}
}
// CGI needs it as REMOTE_USER
if (ah->user != NULL) {
conn->request_info.remote_user = mg_strdup(ah->user);
} else {
return 0;
}
return 1;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct mg_connection *conn, FILE *fp) {
struct ah ah;
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
return 0;
}
// Loop over passwords file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
continue;
}
if (!strcmp(ah.user, f_user) &&
!strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
return check_password(conn->request_info.request_method, ha1, ah.uri,
ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
}
return 0;
}
// Return 1 if request is authorised, 0 otherwise.
static int check_authorization(struct mg_connection *conn, const char *path) {
char fname[PATH_MAX];
struct vec uri_vec, filename_vec;
const char *list;
FILE *fp = NULL;
int authorized = 1;
list = conn->ctx->config[PROTECT_URI];
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
mg_snprintf(fname, sizeof(fname), "%.*s",
(int) filename_vec.len, filename_vec.ptr);
if ((fp = mg_fopen(fname, "r")) == NULL) {
cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
}
break;
}
}
if (fp == NULL) {
fp = open_auth_file(conn, path);
}
if (fp != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static void send_authorization_request(struct mg_connection *conn) {
conn->status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL));
}
static int is_authorized_for_put(struct mg_connection *conn) {
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
FILE *fp;
int ret = 0;
if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
ret = authorize(conn, fp);
fclose(fp);
}
return ret;
}
int mg_modify_passwords_file(const char *fname, const char *domain,
const char *user, const char *pass) {
int found;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
FILE *fp, *fp2;
found = 0;
fp = fp2 = NULL;
// Regard empty password as no password - remove user record.
if (pass != NULL && pass[0] == '\0') {
pass = NULL;
}
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist
if ((fp = fopen(fname, "a+")) != NULL) {
fclose(fp);
}
// Open the given file and temporary file
if ((fp = fopen(fname, "r")) == NULL) {
return 0;
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp);
return 0;
}
// Copy the stuff to temporary file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
continue;
}
if (!strcmp(u, user) && !strcmp(d, domain)) {
found++;
if (pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
} else {
fprintf(fp2, "%s", line);
}
}
// If new user, just add it
if (!found && pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
// Close files
fclose(fp);
fclose(fp2);
// Put the temp file in place of real file
remove(fname);
rename(tmp, fname);
return 1;
}
static SOCKET conn2(const char *host, int port, int use_ssl, static SOCKET conn2(const char *host, int port, int use_ssl,
char *ebuf, size_t ebuf_len) { char *ebuf, size_t ebuf_len) {
struct sockaddr_in sin; struct sockaddr_in sin;
......
...@@ -458,6 +458,8 @@ struct de { ...@@ -458,6 +458,8 @@ struct de {
struct file file; struct file file;
}; };
static FILE *mg_fopen(const char *path, const char *mode);
static int mg_stat(const char *path, struct file *filep);
static void mg_strlcpy(register char *dst, register const char *src, size_t n) { static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
for (; *src != '\0' && n > 1; n--) { for (; *src != '\0' && n > 1; n--) {
...@@ -806,6 +808,502 @@ const char *mg_get_option(const struct mg_context *ctx, const char *name) { ...@@ -806,6 +808,502 @@ const char *mg_get_option(const struct mg_context *ctx, const char *name) {
return ctx->config[i]; return ctx->config[i];
} }
} }
static int is_big_endian(void) {
static const int n = 1;
return ((char *) &n)[0] == 0;
}
#ifndef HAVE_MD5
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
}
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
}
memcpy(ctx->in, buf, len);
}
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
} else {
memset(p, 0, count - 8);
}
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
}
*to = '\0';
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx);
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
}
va_end(ap);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
qop == NULL || response == NULL) {
return 0;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
char name[PATH_MAX];
const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
struct file file = STRUCT_FILE_INITIALIZER;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
fp = mg_fopen(gpass, "r");
// Important: using local struct file to test path for is_directory flag.
// If filep is used, mg_stat() makes it appear as if auth file was opened.
} else if (mg_stat(path, &file) && file.is_directory) {
mg_snprintf(name, sizeof(name), "%s%c%s",
path, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
} else {
// Try to find .htpasswd in requested directory.
for (p = path, e = p + strlen(p) - 1; e > p; e--)
if (e[0] == '/')
break;
mg_snprintf(name, sizeof(name), "%.*s%c%s",
(int) (e - p), p, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
}
return fp;
}
// Parsed Authorization header
struct ah {
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
};
// Return 1 on success. Always initializes the ah structure.
static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) {
char *name, *value, *s;
const char *auth_header;
(void) memset(ah, 0, sizeof(*ah));
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0;
}
// Make modifiable copy of the auth header
(void) mg_strlcpy(buf, auth_header + 7, buf_size);
s = buf;
// Parse authorization header
for (;;) {
// Gobble initial spaces
while (isspace(* (unsigned char *) s)) {
s++;
}
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
}
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
}
if (*name == '\0') {
break;
}
if (!strcmp(name, "username")) {
ah->user = value;
} else if (!strcmp(name, "cnonce")) {
ah->cnonce = value;
} else if (!strcmp(name, "response")) {
ah->response = value;
} else if (!strcmp(name, "uri")) {
ah->uri = value;
} else if (!strcmp(name, "qop")) {
ah->qop = value;
} else if (!strcmp(name, "nc")) {
ah->nc = value;
} else if (!strcmp(name, "nonce")) {
ah->nonce = value;
}
}
// CGI needs it as REMOTE_USER
if (ah->user != NULL) {
conn->request_info.remote_user = mg_strdup(ah->user);
} else {
return 0;
}
return 1;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct mg_connection *conn, FILE *fp) {
struct ah ah;
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
return 0;
}
// Loop over passwords file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
continue;
}
if (!strcmp(ah.user, f_user) &&
!strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
return check_password(conn->request_info.request_method, ha1, ah.uri,
ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
}
return 0;
}
// Return 1 if request is authorised, 0 otherwise.
static int check_authorization(struct mg_connection *conn, const char *path) {
char fname[PATH_MAX];
struct vec uri_vec, filename_vec;
const char *list;
FILE *fp = NULL;
int authorized = 1;
list = conn->ctx->config[PROTECT_URI];
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
mg_snprintf(fname, sizeof(fname), "%.*s",
(int) filename_vec.len, filename_vec.ptr);
fp = mg_fopen(fname, "r");
break;
}
}
if (fp == NULL) {
fp = open_auth_file(conn, path);
}
if (fp != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static void send_authorization_request(struct mg_connection *conn) {
conn->status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL));
}
static int is_authorized_for_put(struct mg_connection *conn) {
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
FILE *fp;
int ret = 0;
if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
ret = authorize(conn, fp);
fclose(fp);
}
return ret;
}
int mg_modify_passwords_file(const char *fname, const char *domain,
const char *user, const char *pass) {
int found;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
FILE *fp, *fp2;
found = 0;
fp = fp2 = NULL;
// Regard empty password as no password - remove user record.
if (pass != NULL && pass[0] == '\0') {
pass = NULL;
}
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist
if ((fp = fopen(fname, "a+")) != NULL) {
fclose(fp);
}
// Open the given file and temporary file
if ((fp = fopen(fname, "r")) == NULL) {
return 0;
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp);
return 0;
}
// Copy the stuff to temporary file
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
continue;
}
if (!strcmp(u, user) && !strcmp(d, domain)) {
found++;
if (pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
} else {
fprintf(fp2, "%s", line);
}
}
// If new user, just add it
if (!found && pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
// Close files
fclose(fp);
fclose(fp2);
// Put the temp file in place of real file
remove(fname);
rename(tmp, fname);
return 1;
}
// Return number of bytes left to read for this connection // Return number of bytes left to read for this connection
static int64_t left_to_read(const struct mg_connection *conn) { static int64_t left_to_read(const struct mg_connection *conn) {
...@@ -995,1520 +1493,1021 @@ static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) { ...@@ -995,1520 +1493,1021 @@ static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
} }
static int pthread_cond_signal(pthread_cond_t *cv) { static int pthread_cond_signal(pthread_cond_t *cv) {
return SetEvent(cv->signal) == 0 ? -1 : 0; return SetEvent(cv->signal) == 0 ? -1 : 0;
}
static int pthread_cond_broadcast(pthread_cond_t *cv) {
// Implementation with PulseEvent() has race condition, see
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
}
static int pthread_cond_destroy(pthread_cond_t *cv) {
return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
}
// For Windows, change all slashes to backslashes in path names.
static void change_slashes_to_backslashes(char *path) {
int i;
for (i = 0; path[i] != '\0'; i++) {
if (path[i] == '/')
path[i] = '\\';
// i > 0 check is to preserve UNC paths, like \\server\file.txt
if (path[i] == '\\' && i > 0)
while (path[i + 1] == '\\' || path[i + 1] == '/')
(void) memmove(path + i + 1,
path + i + 2, strlen(path + i + 1));
}
}
// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
// wbuf and wbuf_len is a target buffer and its length.
static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
char buf[PATH_MAX * 2], buf2[PATH_MAX * 2];
mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf);
// Convert to Unicode and back. If doubly-converted string does not
// match the original, something is fishy, reject.
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
NULL, NULL);
if (strcmp(buf, buf2) != 0) {
wbuf[0] = L'\0';
}
}
#if defined(_WIN32_WCE)
static time_t time(time_t *ptime) {
time_t t;
SYSTEMTIME st;
FILETIME ft;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
if (ptime != NULL) {
*ptime = t;
}
return t;
}
static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
FILETIME ft, lft;
SYSTEMTIME st;
TIME_ZONE_INFORMATION tzinfo;
if (ptm == NULL) {
return NULL;
}
* (int64_t *) &ft = t;
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, &st);
ptm->tm_year = st.wYear - 1900;
ptm->tm_mon = st.wMonth - 1;
ptm->tm_wday = st.wDayOfWeek;
ptm->tm_mday = st.wDay;
ptm->tm_hour = st.wHour;
ptm->tm_min = st.wMinute;
ptm->tm_sec = st.wSecond;
ptm->tm_yday = 0; // hope nobody uses this
ptm->tm_isdst =
GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
return ptm;
}
static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
// FIXME(lsm): fix this.
return localtime(ptime, ptm);
}
static size_t strftime(char *dst, size_t dst_size, const char *fmt,
const struct tm *tm) {
(void) snprintf(dst, dst_size, "implement strftime() for WinCE");
return 0;
}
#endif
// Windows happily opens files with some garbage at the end of file name.
// For example, fopen("a.cgi ", "r") on Windows successfully opens
// "a.cgi", despite one would expect an error back.
// This function returns non-0 if path ends with some garbage.
static int path_cannot_disclose_cgi(const char *path) {
static const char *allowed_last_characters = "_-";
int last = path[strlen(path) - 1];
return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
}
static int mg_stat(const char *path, struct file *filep) {
wchar_t wbuf[PATH_MAX] = L"\\\\?\\";
WIN32_FILE_ATTRIBUTE_DATA info;
filep->modification_time = 0;
to_unicode(path, wbuf + 4, ARRAY_SIZE(wbuf) - 4);
if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
filep->modification_time = SYS2UNIX_TIME(
info.ftLastWriteTime.dwLowDateTime,
info.ftLastWriteTime.dwHighDateTime);
filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
// If file name is fishy, reset the file structure and return error.
// Note it is important to reset, not just return the error, cause
// functions like is_file_opened() check the struct.
if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
memset(filep, 0, sizeof(*filep));
}
}
return filep->modification_time != 0;
}
static int mg_remove(const char *path) {
wchar_t wbuf[PATH_MAX];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
return DeleteFileW(wbuf) ? 0 : -1;
}
static int mg_mkdir(const char *path, int mode) {
char buf[PATH_MAX];
wchar_t wbuf[PATH_MAX];
(void) mode;
mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf);
(void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
}
// Implementation of POSIX opendir/closedir/readdir for Windows.
static DIR * opendir(const char *name) {
DIR *dir = NULL;
wchar_t wpath[PATH_MAX];
DWORD attrs;
if (name == NULL) {
SetLastError(ERROR_BAD_ARGUMENTS);
} else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
} else {
to_unicode(name, wpath, ARRAY_SIZE(wpath));
attrs = GetFileAttributesW(wpath);
if (attrs != 0xFFFFFFFF &&
((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
(void) wcscat(wpath, L"\\*");
dir->handle = FindFirstFileW(wpath, &dir->info);
dir->result.d_name[0] = '\0';
} else {
free(dir);
dir = NULL;
}
}
return dir;
}
static int closedir(DIR *dir) {
int result = 0;
if (dir != NULL) {
if (dir->handle != INVALID_HANDLE_VALUE)
result = FindClose(dir->handle) ? 0 : -1;
free(dir);
} else {
result = -1;
SetLastError(ERROR_BAD_ARGUMENTS);
}
return result;
}
static struct dirent *readdir(DIR *dir) {
struct dirent *result = 0;
if (dir) {
if (dir->handle != INVALID_HANDLE_VALUE) {
result = &dir->result;
(void) WideCharToMultiByte(CP_UTF8, 0,
dir->info.cFileName, -1, result->d_name,
sizeof(result->d_name), NULL, NULL);
if (!FindNextFileW(dir->handle, &dir->info)) {
(void) FindClose(dir->handle);
dir->handle = INVALID_HANDLE_VALUE;
}
} else {
SetLastError(ERROR_FILE_NOT_FOUND);
}
} else {
SetLastError(ERROR_BAD_ARGUMENTS);
}
return result;
}
#ifndef HAVE_POLL
static int poll(struct pollfd *pfd, int n, int milliseconds) {
struct timeval tv;
fd_set set;
int i, result;
SOCKET maxfd = 0;
tv.tv_sec = milliseconds / 1000;
tv.tv_usec = (milliseconds % 1000) * 1000;
FD_ZERO(&set);
for (i = 0; i < n; i++) {
FD_SET((SOCKET) pfd[i].fd, &set);
pfd[i].revents = 0;
if (pfd[i].fd > maxfd) {
maxfd = pfd[i].fd;
}
}
if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) {
for (i = 0; i < n; i++) {
if (FD_ISSET(pfd[i].fd, &set)) {
pfd[i].revents = POLLIN;
}
}
}
return result;
}
#endif // HAVE_POLL
static void set_close_on_exec(SOCKET sock) {
(void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
} }
int mg_start_thread(mg_thread_func_t f, void *p) { static int pthread_cond_broadcast(pthread_cond_t *cv) {
return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0; // Implementation with PulseEvent() has race condition, see
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
} }
static HANDLE dlopen(const char *dll_name, int flags) { static int pthread_cond_destroy(pthread_cond_t *cv) {
wchar_t wbuf[PATH_MAX]; return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
(void) flags;
to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
return LoadLibraryW(wbuf);
} }
#if !defined(NO_CGI) // For Windows, change all slashes to backslashes in path names.
#define SIGKILL 0 static void change_slashes_to_backslashes(char *path) {
static int kill(pid_t pid, int sig_num) { int i;
(void) TerminateProcess(pid, sig_num);
(void) CloseHandle(pid);
return 0;
}
static void trim_trailing_whitespaces(char *s) { for (i = 0; path[i] != '\0'; i++) {
char *e = s + strlen(s) - 1; if (path[i] == '/')
while (e > s && isspace(* (unsigned char *) e)) { path[i] = '\\';
*e-- = '\0'; // i > 0 check is to preserve UNC paths, like \\server\file.txt
if (path[i] == '\\' && i > 0)
while (path[i + 1] == '\\' || path[i + 1] == '/')
(void) memmove(path + i + 1,
path + i + 2, strlen(path + i + 1));
} }
} }
static pid_t spawn_process(struct mg_connection *conn, const char *prog, // Encode 'path' which is assumed UTF-8 string, into UNICODE string.
char *envblk, char *envp[], int fdin, // wbuf and wbuf_len is a target buffer and its length.
int fdout, const char *dir) { static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
HANDLE me; char buf[PATH_MAX * 2], buf2[PATH_MAX * 2];
char *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
cmdline[PATH_MAX], buf[PATH_MAX];
FILE *fp;
STARTUPINFOA si;
PROCESS_INFORMATION pi = { 0 };
(void) envp;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
// TODO(lsm): redirect CGI errors to the error log file
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
me = GetCurrentProcess();
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
&si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
&si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
// If CGI file is a script, try to read the interpreter line
interp = conn->ctx->config[CGI_INTERPRETER];
if (interp == NULL) {
buf[0] = buf[1] = '\0';
// Read the first line of the script into the buffer mg_strlcpy(buf, path, sizeof(buf));
snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog); change_slashes_to_backslashes(buf);
if ((fp = mg_fopen(cmdline, "r")) != NULL) {
fgets(buf, sizeof(buf), fp);
fclose(fp);
buf[sizeof(buf) - 1] = '\0';
}
if (buf[0] == '#' && buf[1] == '!') { // Convert to Unicode and back. If doubly-converted string does not
trim_trailing_whitespaces(buf + 2); // match the original, something is fishy, reject.
} else { memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
buf[2] = '\0'; MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
} WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
interp = buf + 2; NULL, NULL);
if (strcmp(buf, buf2) != 0) {
wbuf[0] = L'\0';
} }
}
if (interp[0] != '\0') { #if defined(_WIN32_WCE)
GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL); static time_t time(time_t *ptime) {
interp = full_interp; time_t t;
} SYSTEMTIME st;
GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL); FILETIME ft;
mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"", GetSystemTime(&st);
interp, interp[0] == '\0' ? "" : " ", full_dir, prog); SystemTimeToFileTime(&st, &ft);
t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
DEBUG_TRACE(("Running [%s]", cmdline)); if (ptime != NULL) {
if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, *ptime = t;
CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
cry(conn, "%s: CreateProcess(%s): %ld",
__func__, cmdline, ERRNO);
pi.hProcess = (pid_t) -1;
} }
(void) CloseHandle(si.hStdOutput); return t;
(void) CloseHandle(si.hStdInput);
(void) CloseHandle(pi.hThread);
return (pid_t) pi.hProcess;
}
#endif // !NO_CGI
static int set_non_blocking_mode(SOCKET sock) {
unsigned long on = 1;
return ioctlsocket(sock, FIONBIO, &on);
} }
#else static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
static int mg_stat(const char *path, struct file *filep) { int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
struct stat st; FILETIME ft, lft;
SYSTEMTIME st;
filep->modification_time = (time_t) 0; TIME_ZONE_INFORMATION tzinfo;
if (stat(path, &st) == 0) {
filep->size = st.st_size;
filep->modification_time = st.st_mtime;
filep->is_directory = S_ISDIR(st.st_mode);
// See https://github.com/cesanta/mongoose/issues/109 if (ptm == NULL) {
// Some filesystems report modification time as 0. Artificially return NULL;
// bump it up to mark mg_stat() success.
if (filep->modification_time == (time_t) 0) {
filep->modification_time = (time_t) 1;
}
} }
return filep->modification_time != (time_t) 0; * (int64_t *) &ft = t;
} FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, &st);
ptm->tm_year = st.wYear - 1900;
ptm->tm_mon = st.wMonth - 1;
ptm->tm_wday = st.wDayOfWeek;
ptm->tm_mday = st.wDay;
ptm->tm_hour = st.wHour;
ptm->tm_min = st.wMinute;
ptm->tm_sec = st.wSecond;
ptm->tm_yday = 0; // hope nobody uses this
ptm->tm_isdst =
GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
static void set_close_on_exec(int fd) { return ptm;
fcntl(fd, F_SETFD, FD_CLOEXEC);
} }
int mg_start_thread(mg_thread_func_t func, void *param) { static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
pthread_t thread_id; // FIXME(lsm): fix this.
pthread_attr_t attr; return localtime(ptime, ptm);
int result; }
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#if USE_STACK_SIZE > 1 static size_t strftime(char *dst, size_t dst_size, const char *fmt,
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384 const struct tm *tm) {
(void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE); (void) snprintf(dst, dst_size, "implement strftime() for WinCE");
return 0;
}
#endif #endif
result = pthread_create(&thread_id, &attr, func, param); // Windows happily opens files with some garbage at the end of file name.
pthread_attr_destroy(&attr); // For example, fopen("a.cgi ", "r") on Windows successfully opens
// "a.cgi", despite one would expect an error back.
return result; // This function returns non-0 if path ends with some garbage.
static int path_cannot_disclose_cgi(const char *path) {
static const char *allowed_last_characters = "_-";
int last = path[strlen(path) - 1];
return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
} }
#ifndef NO_CGI static int mg_stat(const char *path, struct file *filep) {
static pid_t spawn_process(struct mg_connection *conn, const char *prog, wchar_t wbuf[PATH_MAX] = L"\\\\?\\";
char *envblk, char *envp[], int fdin, WIN32_FILE_ATTRIBUTE_DATA info;
int fdout, const char *dir) {
pid_t pid;
const char *interp;
(void) envblk;
if ((pid = fork()) == -1) {
// Parent
send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
} else if (pid == 0) {
// Child
if (chdir(dir) != 0) {
cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
} else if (dup2(fdin, 0) == -1) {
cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
} else if (dup2(fdout, 1) == -1) {
cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
} else {
// Not redirecting stderr to stdout, to avoid output being littered
// with the error messages.
(void) close(fdin);
(void) close(fdout);
// After exec, all signal handlers are restored to their default values,
// with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
// implementation, SIGCHLD's handler will leave unchanged after exec
// if it was set to be ignored. Restore it to default action.
signal(SIGCHLD, SIG_DFL);
interp = conn->ctx->config[CGI_INTERPRETER]; filep->modification_time = 0;
if (interp == NULL) { to_unicode(path, wbuf + 4, ARRAY_SIZE(wbuf) - 4);
(void) execle(prog, prog, NULL, envp); if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO)); filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
} else { filep->modification_time = SYS2UNIX_TIME(
(void) execle(interp, interp, prog, NULL, envp); info.ftLastWriteTime.dwLowDateTime,
cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog, info.ftLastWriteTime.dwHighDateTime);
strerror(ERRNO)); filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
} // If file name is fishy, reset the file structure and return error.
// Note it is important to reset, not just return the error, cause
// functions like is_file_opened() check the struct.
if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
memset(filep, 0, sizeof(*filep));
} }
exit(EXIT_FAILURE);
} }
return pid; return filep->modification_time != 0;
} }
#endif // !NO_CGI
static int set_non_blocking_mode(SOCKET sock) {
int flags;
flags = fcntl(sock, F_GETFL, 0);
(void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
return 0; static int mg_remove(const char *path) {
wchar_t wbuf[PATH_MAX];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
return DeleteFileW(wbuf) ? 0 : -1;
} }
#endif // _WIN32
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
int64_t len) {
int64_t sent;
int n, k;
(void) ssl; // Get rid of warning
sent = 0;
while (sent < len) {
// How many bytes we send in this iteration
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
if (ssl != NULL) { static int mg_mkdir(const char *path, int mode) {
n = SSL_write(ssl, buf + sent, k); char buf[PATH_MAX];
} else if (fp != NULL) { wchar_t wbuf[PATH_MAX];
n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
if (ferror(fp))
n = -1;
} else {
n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
}
if (n <= 0) (void) mode;
break; mg_strlcpy(buf, path, sizeof(buf));
change_slashes_to_backslashes(buf);
sent += n; (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
}
return sent; return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
} }
// Read from IO channel - opened file descriptor, socket, or SSL descriptor. // Implementation of POSIX opendir/closedir/readdir for Windows.
// Return negative value on error, or number of bytes read on success. static DIR * opendir(const char *name) {
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) { DIR *dir = NULL;
int nread; wchar_t wpath[PATH_MAX];
DWORD attrs;
if (len <= 0) return 0; if (name == NULL) {
if (fp != NULL) { SetLastError(ERROR_BAD_ARGUMENTS);
// Use read() instead of fread(), because if we're reading from the CGI } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
// pipe, fread() may block until IO buffer is filled up. We cannot afford SetLastError(ERROR_NOT_ENOUGH_MEMORY);
// to block and must pass all read bytes immediately to the client.
nread = read(fileno(fp), buf, (size_t) len);
#ifndef NO_SSL
} else if (conn->ssl != NULL) {
nread = SSL_read(conn->ssl, buf, len);
#endif
} else { } else {
nread = recv(conn->client.sock, buf, (size_t) len, 0); to_unicode(name, wpath, ARRAY_SIZE(wpath));
attrs = GetFileAttributesW(wpath);
if (attrs != 0xFFFFFFFF &&
((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
(void) wcscat(wpath, L"\\*");
dir->handle = FindFirstFileW(wpath, &dir->info);
dir->result.d_name[0] = '\0';
} else {
free(dir);
dir = NULL;
} }
if (nread > 0) {
conn->num_bytes_read += nread;
} }
return conn->ctx->stop_flag ? -1 : nread; return dir;
} }
static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) { static int closedir(DIR *dir) {
int n, nread = 0; int result = 0;
while (len > 0 && conn->ctx->stop_flag == 0) { if (dir != NULL) {
n = pull(fp, conn, buf + nread, len); if (dir->handle != INVALID_HANDLE_VALUE)
if (n < 0) { result = FindClose(dir->handle) ? 0 : -1;
nread = n; // Propagate the error
break; free(dir);
} else if (n == 0) {
break; // No more data to read
} else { } else {
nread += n; result = -1;
len -= n; SetLastError(ERROR_BAD_ARGUMENTS);
}
} }
return nread; return result;
} }
int mg_read(struct mg_connection *conn, void *buf, int len) { static struct dirent *readdir(DIR *dir) {
int n, buffered_len, nread = 0; struct dirent *result = 0;
int64_t left;
// If Content-Length is not set, read until socket is closed
if (conn->content_len <= 0) {
conn->content_len = INT64_MAX;
conn->must_close = 1;
}
// conn->buf body
// |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
// First, check for data buffered in conn->buf by read_request(). if (dir) {
if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) { if (dir->handle != INVALID_HANDLE_VALUE) {
char *body = conn->buf + conn->request_len; result = &dir->result;
if (buffered_len > len) buffered_len = len; (void) WideCharToMultiByte(CP_UTF8, 0,
if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len; dir->info.cFileName, -1, result->d_name,
sizeof(result->d_name), NULL, NULL);
memcpy(buf, body, (size_t) buffered_len); if (!FindNextFileW(dir->handle, &dir->info)) {
memmove(body, body + buffered_len, (void) FindClose(dir->handle);
&conn->buf[conn->data_len] - &body[buffered_len]); dir->handle = INVALID_HANDLE_VALUE;
len -= buffered_len;
conn->data_len -= buffered_len;
nread += buffered_len;
} }
// Read data from the socket. } else {
if (len > 0 && (left = left_to_read(conn)) > 0) { SetLastError(ERROR_FILE_NOT_FOUND);
if (left < len) {
len = (int) left;
} }
n = pull_all(NULL, conn, (char *) buf + nread, (int) len); } else {
nread = n >= 0 ? nread + n : n; SetLastError(ERROR_BAD_ARGUMENTS);
} }
return nread; return result;
} }
int mg_write(struct mg_connection *conn, const void *buf, int len) { #ifndef HAVE_POLL
time_t now; static int poll(struct pollfd *pfd, int n, int milliseconds) {
int64_t n, total, allowed; struct timeval tv;
fd_set set;
int i, result;
SOCKET maxfd = 0;
if (conn->throttle > 0) { tv.tv_sec = milliseconds / 1000;
if ((now = time(NULL)) != conn->last_throttle_time) { tv.tv_usec = (milliseconds % 1000) * 1000;
conn->last_throttle_time = now; FD_ZERO(&set);
conn->last_throttle_bytes = 0;
} for (i = 0; i < n; i++) {
allowed = conn->throttle - conn->last_throttle_bytes; FD_SET((SOCKET) pfd[i].fd, &set);
if (allowed > (int64_t) len) { pfd[i].revents = 0;
allowed = len;
if (pfd[i].fd > maxfd) {
maxfd = pfd[i].fd;
} }
if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) == allowed) {
buf = (char *) buf + total;
conn->last_throttle_bytes += total;
while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
allowed = conn->throttle > (int64_t) len - total ?
(int64_t) len - total : conn->throttle;
if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) != allowed) {
break;
} }
sleep(1);
conn->last_throttle_bytes = allowed; if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) {
conn->last_throttle_time = time(NULL); for (i = 0; i < n; i++) {
buf = (char *) buf + n; if (FD_ISSET(pfd[i].fd, &set)) {
total += n; pfd[i].revents = POLLIN;
} }
} }
} else {
total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) len);
} }
return (int) total;
return result;
} }
#endif // HAVE_POLL
// Print message to buffer. If buffer is large enough to hold the message, static void set_close_on_exec(SOCKET sock) {
// return buffer. If buffer is to small, allocate large enough buffer on heap, (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
// and return allocated buffer. }
static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
va_list ap_copy;
int len;
// Windows is not standard-compliant, and vsnprintf() returns -1 if int mg_start_thread(mg_thread_func_t f, void *p) {
// buffer is too small. Also, older versions of msvcrt.dll do not have return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
// _vscprintf(). However, if size is 0, vsnprintf() behaves correctly. }
// Therefore, we make two passes: on first pass, get required message length.
// On second pass, actually print the message.
va_copy(ap_copy, ap);
len = vsnprintf(NULL, 0, fmt, ap_copy);
if (len > (int) size && static HANDLE dlopen(const char *dll_name, int flags) {
(size = len + 1) > 0 && wchar_t wbuf[PATH_MAX];
(*buf = (char *) malloc(size)) == NULL) { (void) flags;
len = -1; // Allocation failed, mark failure to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
} else { return LoadLibraryW(wbuf);
va_copy(ap_copy, ap); }
vsnprintf(*buf, size, fmt, ap_copy);
}
return len; #if !defined(NO_CGI)
#define SIGKILL 0
static int kill(pid_t pid, int sig_num) {
(void) TerminateProcess(pid, sig_num);
(void) CloseHandle(pid);
return 0;
} }
int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) { static void trim_trailing_whitespaces(char *s) {
char mem[MG_BUF_LEN], *buf = mem; char *e = s + strlen(s) - 1;
int len; while (e > s && isspace(* (unsigned char *) e)) {
*e-- = '\0';
if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
len = mg_write(conn, buf, (size_t) len);
}
if (buf != mem && buf != NULL) {
free(buf);
} }
return len;
} }
int mg_printf(struct mg_connection *conn, const char *fmt, ...) { static pid_t spawn_process(struct mg_connection *conn, const char *prog,
va_list ap; char *envblk, char *envp[], int fdin,
va_start(ap, fmt); int fdout, const char *dir) {
return mg_vprintf(conn, fmt, ap); HANDLE me;
} char *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
cmdline[PATH_MAX], buf[PATH_MAX];
FILE *fp;
STARTUPINFOA si;
PROCESS_INFORMATION pi = { 0 };
static int mg_chunked_printf(struct mg_connection *conn, const char *fmt, ...) { (void) envp;
char mem[MG_BUF_LEN], *buf = mem;
int len;
va_list ap; memset(&si, 0, sizeof(si));
va_start(ap, fmt); si.cb = sizeof(si);
if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
len = mg_printf(conn, "%X\r\n%s\r\n", len, buf);
}
if (buf != mem && buf != NULL) { // TODO(lsm): redirect CGI errors to the error log file
free(buf); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
} si.wShowWindow = SW_HIDE;
return len; me = GetCurrentProcess();
} DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
&si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
&si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
int mg_url_decode(const char *src, int src_len, char *dst, // If CGI file is a script, try to read the interpreter line
int dst_len, int is_form_url_encoded) { interp = conn->ctx->config[CGI_INTERPRETER];
int i, j, a, b; if (interp == NULL) {
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') buf[0] = buf[1] = '\0';
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { // Read the first line of the script into the buffer
if (src[i] == '%' && i < src_len - 2 && snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
isxdigit(* (const unsigned char *) (src + i + 1)) && if ((fp = mg_fopen(cmdline, "r")) != NULL) {
isxdigit(* (const unsigned char *) (src + i + 2))) { fgets(buf, sizeof(buf), fp);
a = tolower(* (const unsigned char *) (src + i + 1)); fclose(fp);
b = tolower(* (const unsigned char *) (src + i + 2)); buf[sizeof(buf) - 1] = '\0';
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); }
i += 2;
} else if (is_form_url_encoded && src[i] == '+') { if (buf[0] == '#' && buf[1] == '!') {
dst[j] = ' '; trim_trailing_whitespaces(buf + 2);
} else { } else {
dst[j] = src[i]; buf[2] = '\0';
} }
interp = buf + 2;
} }
dst[j] = '\0'; // Null-terminate the destination if (interp[0] != '\0') {
GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL);
interp = full_interp;
}
GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL);
return i >= src_len ? j : -1; mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"",
} interp, interp[0] == '\0' ? "" : " ", full_dir, prog);
int mg_get_var(const char *data, size_t data_len, const char *name, DEBUG_TRACE(("Running [%s]", cmdline));
char *dst, size_t dst_len) { if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
const char *p, *e, *s; CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
size_t name_len; cry(conn, "%s: CreateProcess(%s): %ld",
int len; __func__, cmdline, ERRNO);
pi.hProcess = (pid_t) -1;
}
if (dst == NULL || dst_len == 0) { (void) CloseHandle(si.hStdOutput);
len = -2; (void) CloseHandle(si.hStdInput);
} else if (data == NULL || name == NULL || data_len == 0) { (void) CloseHandle(pi.hThread);
len = -1;
dst[0] = '\0';
} else {
name_len = strlen(name);
e = data + data_len;
len = -1;
dst[0] = '\0';
// data is "var1=val1&var2=val2...". Find variable first return (pid_t) pi.hProcess;
for (p = data; p + name_len < e; p++) { }
if ((p == data || p[-1] == '&') && p[name_len] == '=' && #endif // !NO_CGI
!mg_strncasecmp(name, p, name_len)) {
// Point p to variable value static int set_non_blocking_mode(SOCKET sock) {
p += name_len + 1; unsigned long on = 1;
return ioctlsocket(sock, FIONBIO, &on);
}
// Point s to the end of the value #else
s = (const char *) memchr(p, '&', (size_t)(e - p)); static int mg_stat(const char *path, struct file *filep) {
if (s == NULL) { struct stat st;
s = e;
}
assert(s >= p);
// Decode variable into destination buffer filep->modification_time = (time_t) 0;
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1); if (stat(path, &st) == 0) {
filep->size = st.st_size;
filep->modification_time = st.st_mtime;
filep->is_directory = S_ISDIR(st.st_mode);
// Redirect error code from -1 to -2 (destination buffer too small). // See https://github.com/cesanta/mongoose/issues/109
if (len == -1) { // Some filesystems report modification time as 0. Artificially
len = -2; // bump it up to mark mg_stat() success.
} if (filep->modification_time == (time_t) 0) {
break; filep->modification_time = (time_t) 1;
}
} }
} }
return len; return filep->modification_time != (time_t) 0;
} }
int mg_get_cookie(const char *cookie_header, const char *var_name, static void set_close_on_exec(int fd) {
char *dst, size_t dst_size) { fcntl(fd, F_SETFD, FD_CLOEXEC);
const char *s, *p, *end; }
int name_len, len = -1;
if (dst == NULL || dst_size == 0) { int mg_start_thread(mg_thread_func_t func, void *param) {
len = -2; pthread_t thread_id;
} else if (var_name == NULL || (s = cookie_header) == NULL) { pthread_attr_t attr;
len = -1; int result;
dst[0] = '\0';
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#if USE_STACK_SIZE > 1
// Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
(void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
#endif
result = pthread_create(&thread_id, &attr, func, param);
pthread_attr_destroy(&attr);
return result;
}
#ifndef NO_CGI
static pid_t spawn_process(struct mg_connection *conn, const char *prog,
char *envblk, char *envp[], int fdin,
int fdout, const char *dir) {
pid_t pid;
const char *interp;
(void) envblk;
if ((pid = fork()) == -1) {
// Parent
send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
} else if (pid == 0) {
// Child
if (chdir(dir) != 0) {
cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
} else if (dup2(fdin, 0) == -1) {
cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
} else if (dup2(fdout, 1) == -1) {
cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
} else { } else {
name_len = (int) strlen(var_name); // Not redirecting stderr to stdout, to avoid output being littered
end = s + strlen(s); // with the error messages.
dst[0] = '\0'; (void) close(fdin);
(void) close(fdout);
for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) { // After exec, all signal handlers are restored to their default values,
if (s[name_len] == '=') { // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
s += name_len + 1; // implementation, SIGCHLD's handler will leave unchanged after exec
if ((p = strchr(s, ' ')) == NULL) // if it was set to be ignored. Restore it to default action.
p = end; signal(SIGCHLD, SIG_DFL);
if (p[-1] == ';')
p--; interp = conn->ctx->config[CGI_INTERPRETER];
if (*s == '"' && p[-1] == '"' && p > s + 1) { if (interp == NULL) {
s++; (void) execle(prog, prog, NULL, envp);
p--; cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
}
if ((size_t) (p - s) < dst_size) {
len = p - s;
mg_strlcpy(dst, s, (size_t) len + 1);
} else { } else {
len = -3; (void) execle(interp, interp, prog, NULL, envp);
} cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
break; strerror(ERRNO));
} }
} }
exit(EXIT_FAILURE);
} }
return len;
return pid;
} }
#endif // !NO_CGI
// Return 1 if real file has been found, 0 otherwise static int set_non_blocking_mode(SOCKET sock) {
static int convert_uri_to_file_name(struct mg_connection *conn, char *buf, int flags;
size_t buf_len, struct file *filep) {
struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri,
*root = conn->ctx->config[DOCUMENT_ROOT];
char *p;
int match_len;
char gz_path[PATH_MAX];
char const* accept_encoding;
// No filesystem access flags = fcntl(sock, F_GETFL, 0);
if (root == NULL) { (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
return 0;
}
// Using buf_len - 1 because memmove() for PATH_INFO may shift part return 0;
// of the path one byte on the right. }
// If document_root is NULL, leave the file empty. #endif // _WIN32
mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
rewrite = conn->ctx->config[REWRITE]; // Write data to the IO channel - opened file descriptor, socket or SSL
while ((rewrite = next_option(rewrite, &a, &b)) != NULL) { // descriptor. Return number of bytes written.
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) { static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr, int64_t len) {
uri + match_len); int64_t sent;
break; int n, k;
}
}
if (mg_stat(buf, filep)) { (void) ssl; // Get rid of warning
return 1; sent = 0;
} while (sent < len) {
// if we can't find the actual file, look for the file // How many bytes we send in this iteration
// with the same name but a .gz extension. If we find it, k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
if (strstr(accept_encoding,"gzip") != NULL) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
if (mg_stat(gz_path, filep)) {
filep->gzipped = 1;
return 1;
}
}
}
// Support PATH_INFO for CGI scripts. if (ssl != NULL) {
for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) { n = SSL_write(ssl, buf + sent, k);
if (*p == '/') { } else if (fp != NULL) {
*p = '\0'; n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], if (ferror(fp))
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 && n = -1;
mg_stat(buf, filep)) {
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn->path_info = p + 1;
memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
p[1] = '/';
return 1;
} else { } else {
*p = '/'; n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
}
} }
if (n <= 0)
break;
sent += n;
} }
return 0; return sent;
} }
// Check whether full request is buffered. Return: // Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// -1 if request is malformed // Return negative value on error, or number of bytes read on success.
// 0 if request is not yet fully buffered static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
// >0 actual request length, including last \r\n\r\n int nread;
static int get_request_len(const char *buf, int buf_len) {
int i;
for (i = 0; i < buf_len; i++) { if (len <= 0) return 0;
// Control characters are not allowed but >=128 is. if (fp != NULL) {
// Abort scan as soon as one malformed character is found; // Use read() instead of fread(), because if we're reading from the CGI
// don't let subsequent \r\n\r\n win us over anyhow // pipe, fread() may block until IO buffer is filled up. We cannot afford
if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' && // to block and must pass all read bytes immediately to the client.
buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) { nread = read(fileno(fp), buf, (size_t) len);
return -1; #ifndef NO_SSL
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') { } else if (conn->ssl != NULL) {
return i + 2; nread = SSL_read(conn->ssl, buf, len);
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' && #endif
buf[i + 2] == '\n') { } else {
return i + 3; nread = recv(conn->client.sock, buf, (size_t) len, 0);
} }
if (nread > 0) {
conn->num_bytes_read += nread;
} }
return 0; return conn->ctx->stop_flag ? -1 : nread;
} }
// Protect against directory disclosure attack by removing '..', static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
// excessive '/' and '\' characters int n, nread = 0;
static void remove_double_dots_and_double_slashes(char *s) {
char *p = s;
while (*s != '\0') { while (len > 0 && conn->ctx->stop_flag == 0) {
*p++ = *s++; n = pull(fp, conn, buf + nread, len);
if (s[-1] == '/' || s[-1] == '\\') { if (n < 0) {
// Skip all following slashes, backslashes and double-dots nread = n; // Propagate the error
while (s[0] != '\0') {
if (s[0] == '/' || s[0] == '\\') {
s++;
} else if (s[0] == '.' && s[1] == '.') {
s += 2;
} else {
break; break;
} else if (n == 0) {
break; // No more data to read
} else {
nread += n;
len -= n;
} }
} }
}
}
*p = '\0';
}
static const struct {
const char *extension;
size_t ext_len;
const char *mime_type;
} builtin_mime_types[] = {
{".html", 5, "text/html"},
{".htm", 4, "text/html"},
{".shtm", 5, "text/html"},
{".shtml", 6, "text/html"},
{".css", 4, "text/css"},
{".js", 3, "application/x-javascript"},
{".ico", 4, "image/x-icon"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".jpeg", 5, "image/jpeg"},
{".png", 4, "image/png"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "application/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xml", 4, "text/xml"},
{".json", 5, "text/json"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mov", 4, "video/quicktime"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{".ttf", 4, "application/x-font-ttf"},
{NULL, 0, NULL}
};
const char *mg_get_builtin_mime_type(const char *path) { return nread;
const char *ext; }
size_t i, path_len;
path_len = strlen(path); int mg_read(struct mg_connection *conn, void *buf, int len) {
int n, buffered_len, nread = 0;
int64_t left;
for (i = 0; builtin_mime_types[i].extension != NULL; i++) { // If Content-Length is not set, read until socket is closed
ext = path + (path_len - builtin_mime_types[i].ext_len); if (conn->content_len <= 0) {
if (path_len > builtin_mime_types[i].ext_len && conn->content_len = INT64_MAX;
mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) { conn->must_close = 1;
return builtin_mime_types[i].mime_type;
}
} }
return "text/plain"; // conn->buf body
} // |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
// Look at the "path" extension and figure what mime type it has. // First, check for data buffered in conn->buf by read_request().
// Store mime type in the vector. if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
static void get_mime_type(struct mg_context *ctx, const char *path, char *body = conn->buf + conn->request_len;
struct vec *vec) { if (buffered_len > len) buffered_len = len;
struct vec ext_vec, mime_vec; if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
const char *list, *ext;
size_t path_len;
path_len = strlen(path); memcpy(buf, body, (size_t) buffered_len);
memmove(body, body + buffered_len,
&conn->buf[conn->data_len] - &body[buffered_len]);
len -= buffered_len;
conn->data_len -= buffered_len;
nread += buffered_len;
}
// Scan user-defined mime types first, in case user wants to // Read data from the socket.
// override default mime types. if (len > 0 && (left = left_to_read(conn)) > 0) {
list = ctx->config[EXTRA_MIME_TYPES]; if (left < len) {
while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { len = (int) left;
// ext now points to the path suffix
ext = path + path_len - ext_vec.len;
if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
*vec = mime_vec;
return;
} }
n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
nread = n >= 0 ? nread + n : n;
} }
vec->ptr = mg_get_builtin_mime_type(path); return nread;
vec->len = strlen(vec->ptr);
}
static int is_big_endian(void) {
static const int n = 1;
return ((char *) &n)[0] == 0;
} }
#ifndef HAVE_MD5 int mg_write(struct mg_connection *conn, const void *buf, int len) {
typedef struct MD5Context { time_t now;
uint32_t buf[4]; int64_t n, total, allowed;
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN if (conn->throttle > 0) {
if (is_big_endian()) { if ((now = time(NULL)) != conn->last_throttle_time) {
do { conn->last_throttle_time = now;
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | conn->last_throttle_bytes = 0;
((unsigned) buf[1] << 8 | buf[0]); }
* (uint32_t *) buf = t; allowed = conn->throttle - conn->last_throttle_bytes;
buf += 4; if (allowed > (int64_t) len) {
} while (--longs); allowed = len;
}
if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) == allowed) {
buf = (char *) buf + total;
conn->last_throttle_bytes += total;
while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
allowed = conn->throttle > (int64_t) len - total ?
(int64_t) len - total : conn->throttle;
if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) != allowed) {
break;
}
sleep(1);
conn->last_throttle_bytes = allowed;
conn->last_throttle_time = time(NULL);
buf = (char *) buf + n;
total += n;
}
}
} else {
total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) len);
} }
return (int) total;
} }
#define F1(x, y, z) (z ^ (x & (y ^ z))) // Print message to buffer. If buffer is large enough to hold the message,
#define F2(x, y, z) F1(z, x, y) // return buffer. If buffer is to small, allocate large enough buffer on heap,
#define F3(x, y, z) (x ^ y ^ z) // and return allocated buffer.
#define F4(x, y, z) (y ^ (x | ~z)) static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
va_list ap_copy;
int len;
#define MD5STEP(f, w, x, y, z, data, s) \ // Windows is not standard-compliant, and vsnprintf() returns -1 if
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) // buffer is too small. Also, older versions of msvcrt.dll do not have
// _vscprintf(). However, if size is 0, vsnprintf() behaves correctly.
// Therefore, we make two passes: on first pass, get required message length.
// On second pass, actually print the message.
va_copy(ap_copy, ap);
len = vsnprintf(NULL, 0, fmt, ap_copy);
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious if (len > (int) size &&
// initialization constants. (size = len + 1) > 0 &&
static void MD5Init(MD5_CTX *ctx) { (*buf = (char *) malloc(size)) == NULL) {
ctx->buf[0] = 0x67452301; len = -1; // Allocation failed, mark failure
ctx->buf[1] = 0xefcdab89; } else {
ctx->buf[2] = 0x98badcfe; va_copy(ap_copy, ap);
ctx->buf[3] = 0x10325476; vsnprintf(*buf, size, fmt, ap_copy);
}
ctx->bits[0] = 0; return len;
ctx->bits[1] = 0;
} }
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) { int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
register uint32_t a, b, c, d; char mem[MG_BUF_LEN], *buf = mem;
int len;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); len = mg_write(conn, buf, (size_t) len);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); }
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); if (buf != mem && buf != NULL) {
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); free(buf);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); }
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a; return len;
buf[1] += b;
buf[2] += c;
buf[3] += d;
} }
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) { int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
uint32_t t; va_list ap;
va_start(ap, fmt);
t = ctx->bits[0]; return mg_vprintf(conn, fmt, ap);
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) }
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) { static int mg_chunked_printf(struct mg_connection *conn, const char *fmt, ...) {
unsigned char *p = (unsigned char *) ctx->in + t; char mem[MG_BUF_LEN], *buf = mem;
int len;
t = 64 - t; va_list ap;
if (len < t) { va_start(ap, fmt);
memcpy(p, buf, len); if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
return; len = mg_printf(conn, "%X\r\n%s\r\n", len, buf);
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
} }
while (len >= 64) { if (buf != mem && buf != NULL) {
memcpy(ctx->in, buf, 64); free(buf);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
} }
memcpy(ctx->in, buf, len); return len;
} }
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) { int mg_url_decode(const char *src, int src_len, char *dst,
unsigned count; int dst_len, int is_form_url_encoded) {
unsigned char *p; int i, j, a, b;
uint32_t *a; #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count; for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
*p++ = 0x80; if (src[i] == '%' && i < src_len - 2 &&
count = 64 - 1 - count; isxdigit(* (const unsigned char *) (src + i + 1)) &&
if (count < 8) { isxdigit(* (const unsigned char *) (src + i + 2))) {
memset(p, 0, count); a = tolower(* (const unsigned char *) (src + i + 1));
byteReverse(ctx->in, 16); b = tolower(* (const unsigned char *) (src + i + 2));
MD5Transform(ctx->buf, (uint32_t *) ctx->in); dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
memset(ctx->in, 0, 56); i += 2;
} else if (is_form_url_encoded && src[i] == '+') {
dst[j] = ' ';
} else { } else {
memset(p, 0, count - 8); dst[j] = src[i];
}
} }
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in; dst[j] = '\0'; // Null-terminate the destination
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in); return i >= src_len ? j : -1;
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
} }
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input, int mg_get_var(const char *data, size_t data_len, const char *name,
// because each byte takes 2 bytes in string representation char *dst, size_t dst_len) {
static void bin2str(char *to, const unsigned char *p, size_t len) { const char *p, *e, *s;
static const char *hex = "0123456789abcdef"; size_t name_len;
int len;
for (; len--; p++) { if (dst == NULL || dst_len == 0) {
*to++ = hex[p[0] >> 4]; len = -2;
*to++ = hex[p[0] & 0x0f]; } else if (data == NULL || name == NULL || data_len == 0) {
} len = -1;
*to = '\0'; dst[0] = '\0';
} } else {
name_len = strlen(name);
e = data + data_len;
len = -1;
dst[0] = '\0';
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes. // data is "var1=val1&var2=val2...". Find variable first
char *mg_md5(char buf[33], ...) { for (p = data; p + name_len < e; p++) {
unsigned char hash[16]; if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
const char *p; !mg_strncasecmp(name, p, name_len)) {
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx); // Point p to variable value
p += name_len + 1;
va_start(ap, buf); // Point s to the end of the value
while ((p = va_arg(ap, const char *)) != NULL) { s = (const char *) memchr(p, '&', (size_t)(e - p));
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p)); if (s == NULL) {
s = e;
} }
va_end(ap); assert(s >= p);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK // Decode variable into destination buffer
static int check_password(const char *method, const char *ha1, const char *uri, len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL // Redirect error code from -1 to -2 (destination buffer too small).
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL || if (len == -1) {
qop == NULL || response == NULL) { len = -2;
return 0; }
break;
}
} }
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
} }
mg_md5(ha2, method, ":", uri, NULL); return len;
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
} }
// Use the global passwords file, if specified by auth_gpass option, int mg_get_cookie(const char *cookie_header, const char *var_name,
// or search for .htpasswd in the requested directory. char *dst, size_t dst_size) {
static FILE *open_auth_file(struct mg_connection *conn, const char *path) { const char *s, *p, *end;
char name[PATH_MAX]; int name_len, len = -1;
const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
struct file file = STRUCT_FILE_INITIALIZER;
FILE *fp = NULL;
if (gpass != NULL) { if (dst == NULL || dst_size == 0) {
// Use global passwords file len = -2;
if ((fp = mg_fopen(gpass, "r")) == NULL) { } else if (var_name == NULL || (s = cookie_header) == NULL) {
cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO)); len = -1;
dst[0] = '\0';
} else {
name_len = (int) strlen(var_name);
end = s + strlen(s);
dst[0] = '\0';
for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
if (s[name_len] == '=') {
s += name_len + 1;
if ((p = strchr(s, ' ')) == NULL)
p = end;
if (p[-1] == ';')
p--;
if (*s == '"' && p[-1] == '"' && p > s + 1) {
s++;
p--;
} }
// Important: using local struct file to test path for is_directory flag. if ((size_t) (p - s) < dst_size) {
// If filep is used, mg_stat() makes it appear as if auth file was opened. len = p - s;
} else if (mg_stat(path, &file) && file.is_directory) { mg_strlcpy(dst, s, (size_t) len + 1);
mg_snprintf(name, sizeof(name), "%s%c%s",
path, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
} else { } else {
// Try to find .htpasswd in requested directory. len = -3;
for (p = path, e = p + strlen(p) - 1; e > p; e--) }
if (e[0] == '/')
break; break;
mg_snprintf(name, sizeof(name), "%.*s%c%s",
(int) (e - p), p, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r");
} }
}
return fp; }
return len;
} }
// Parsed Authorization header // Return 1 if real file has been found, 0 otherwise
struct ah { static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; size_t buf_len, struct file *filep) {
}; struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri,
// Return 1 on success. Always initializes the ah structure. *root = conn->ctx->config[DOCUMENT_ROOT];
static int parse_auth_header(struct mg_connection *conn, char *buf, char *p;
size_t buf_size, struct ah *ah) { int match_len;
char *name, *value, *s; char gz_path[PATH_MAX];
const char *auth_header; char const* accept_encoding;
(void) memset(ah, 0, sizeof(*ah)); // No filesystem access
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL || if (root == NULL) {
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0; return 0;
} }
// Make modifiable copy of the auth header // Using buf_len - 1 because memmove() for PATH_INFO may shift part
(void) mg_strlcpy(buf, auth_header + 7, buf_size); // of the path one byte on the right.
s = buf; // If document_root is NULL, leave the file empty.
mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
// Parse authorization header rewrite = conn->ctx->config[REWRITE];
for (;;) { while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
// Gobble initial spaces if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
while (isspace(* (unsigned char *) s)) { mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
s++; uri + match_len);
} break;
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
} }
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
} }
if (*name == '\0') {
break; if (mg_stat(buf, filep)) {
return 1;
} }
if (!strcmp(name, "username")) { // if we can't find the actual file, look for the file
ah->user = value; // with the same name but a .gz extension. If we find it,
} else if (!strcmp(name, "cnonce")) { // use that and set the gzipped flag in the file struct
ah->cnonce = value; // to indicate that the response need to have the content-
} else if (!strcmp(name, "response")) { // encoding: gzip header
ah->response = value; // we can only do this if the browser declares support
} else if (!strcmp(name, "uri")) { if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
ah->uri = value; if (strstr(accept_encoding,"gzip") != NULL) {
} else if (!strcmp(name, "qop")) { snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
ah->qop = value; if (mg_stat(gz_path, filep)) {
} else if (!strcmp(name, "nc")) { filep->gzipped = 1;
ah->nc = value; return 1;
} else if (!strcmp(name, "nonce")) { }
ah->nonce = value;
} }
} }
// CGI needs it as REMOTE_USER // Support PATH_INFO for CGI scripts.
if (ah->user != NULL) { for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) {
conn->request_info.remote_user = mg_strdup(ah->user); if (*p == '/') {
*p = '\0';
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
mg_stat(buf, filep)) {
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn->path_info = p + 1;
memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
p[1] = '/';
return 1;
} else { } else {
return 0; *p = '/';
}
}
} }
return 1; return 0;
} }
// Authorize against the opened passwords file. Return 1 if authorized. // Check whether full request is buffered. Return:
static int authorize(struct mg_connection *conn, FILE *fp) { // -1 if request is malformed
struct ah ah; // 0 if request is not yet fully buffered
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN]; // >0 actual request length, including last \r\n\r\n
static int get_request_len(const char *buf, int buf_len) {
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) { int i;
return 0;
}
// Loop over passwords file for (i = 0; i < buf_len; i++) {
while (fgets(line, sizeof(line), fp) != NULL) { // Control characters are not allowed but >=128 is.
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) { // Abort scan as soon as one malformed character is found;
continue; // don't let subsequent \r\n\r\n win us over anyhow
if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
return -1;
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
return i + 2;
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
buf[i + 2] == '\n') {
return i + 3;
} }
if (!strcmp(ah.user, f_user) &&
!strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
return check_password(conn->request_info.request_method, ha1, ah.uri,
ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
} }
return 0; return 0;
} }
// Return 1 if request is authorised, 0 otherwise. // Protect against directory disclosure attack by removing '..',
static int check_authorization(struct mg_connection *conn, const char *path) { // excessive '/' and '\' characters
char fname[PATH_MAX]; static void remove_double_dots_and_double_slashes(char *s) {
struct vec uri_vec, filename_vec; char *p = s;
const char *list;
FILE *fp = NULL;
int authorized = 1;
list = conn->ctx->config[PROTECT_URI]; while (*s != '\0') {
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { *p++ = *s++;
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { if (s[-1] == '/' || s[-1] == '\\') {
mg_snprintf(fname, sizeof(fname), "%.*s", // Skip all following slashes, backslashes and double-dots
(int) filename_vec.len, filename_vec.ptr); while (s[0] != '\0') {
if ((fp = mg_fopen(fname, "r")) == NULL) { if (s[0] == '/' || s[0] == '\\') {
cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); s++;
} } else if (s[0] == '.' && s[1] == '.') {
s += 2;
} else {
break; break;
} }
} }
if (fp == NULL) {
fp = open_auth_file(conn, path);
} }
if (fp != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
} }
*p = '\0';
return authorized;
}
static void send_authorization_request(struct mg_connection *conn) {
conn->status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL));
} }
static int is_authorized_for_put(struct mg_connection *conn) { static const struct {
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE]; const char *extension;
FILE *fp; size_t ext_len;
int ret = 0; const char *mime_type;
} builtin_mime_types[] = {
if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) { {".html", 5, "text/html"},
ret = authorize(conn, fp); {".htm", 4, "text/html"},
fclose(fp); {".shtm", 5, "text/html"},
} {".shtml", 6, "text/html"},
{".css", 4, "text/css"},
return ret; {".js", 3, "application/x-javascript"},
} {".ico", 4, "image/x-icon"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".jpeg", 5, "image/jpeg"},
{".png", 4, "image/png"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "application/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xml", 4, "text/xml"},
{".json", 5, "text/json"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mov", 4, "video/quicktime"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{".ttf", 4, "application/x-font-ttf"},
{NULL, 0, NULL}
};
int mg_modify_passwords_file(const char *fname, const char *domain, const char *mg_get_builtin_mime_type(const char *path) {
const char *user, const char *pass) { const char *ext;
int found; size_t i, path_len;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
FILE *fp, *fp2;
found = 0; path_len = strlen(path);
fp = fp2 = NULL;
// Regard empty password as no password - remove user record. for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
if (pass != NULL && pass[0] == '\0') { ext = path + (path_len - builtin_mime_types[i].ext_len);
pass = NULL; if (path_len > builtin_mime_types[i].ext_len &&
mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
return builtin_mime_types[i].mime_type;
} }
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist
if ((fp = fopen(fname, "a+")) != NULL) {
fclose(fp);
} }
// Open the given file and temporary file return "text/plain";
if ((fp = fopen(fname, "r")) == NULL) { }
return 0;
} else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp);
return 0;
}
// Copy the stuff to temporary file // Look at the "path" extension and figure what mime type it has.
while (fgets(line, sizeof(line), fp) != NULL) { // Store mime type in the vector.
if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) { static void get_mime_type(struct mg_context *ctx, const char *path,
continue; struct vec *vec) {
} struct vec ext_vec, mime_vec;
const char *list, *ext;
size_t path_len;
if (!strcmp(u, user) && !strcmp(d, domain)) { path_len = strlen(path);
found++;
if (pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL);
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
}
} else {
fprintf(fp2, "%s", line);
}
}
// If new user, just add it // Scan user-defined mime types first, in case user wants to
if (!found && pass != NULL) { // override default mime types.
mg_md5(ha1, user, ":", domain, ":", pass, NULL); list = ctx->config[EXTRA_MIME_TYPES];
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
// ext now points to the path suffix
ext = path + path_len - ext_vec.len;
if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
*vec = mime_vec;
return;
}
} }
// Close files vec->ptr = mg_get_builtin_mime_type(path);
fclose(fp); vec->len = strlen(vec->ptr);
fclose(fp2);
// Put the temp file in place of real file
remove(fname);
rename(tmp, fname);
return 1;
} }
static SOCKET conn2(const char *host, int port, int use_ssl, static SOCKET conn2(const char *host, int port, int use_ssl,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment