mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-04 23:24:10 +07:00
302 lines
8.2 KiB
C
302 lines
8.2 KiB
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;
|
|
}
|