mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-09 16:21:45 +00:00
Moved auth code to separate file
This commit is contained in:
1 parent
199e0a330f
commit
d3577b7877
6 files changed
+998
-999
No files matched your search
@@ -0,0 +1,301 @@
|
||||
#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;
|
||||
}
|
||||
Reference in new issue
Block a user