adamc@853: #define _GNU_SOURCE adamc@853: adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: adamc@853: #include adamc@853: adamc@853: #include adamc@853: adamc@853: #include "urweb.h" adamc@853: adamc@853: #define MAX_RETRIES 5 adamc@853: adamc@856: static int try_rollback(uw_context ctx, void *logger_data, uw_logger log_error) { adamc@853: int r = uw_rollback(ctx); adamc@853: adamc@853: if (r) { adamc@856: log_error(logger_data, "Error running SQL ROLLBACK\n"); adamc@853: uw_reset(ctx); adamc@853: uw_write(ctx, "HTTP/1.1 500 Internal Server Error\n\r"); adamc@853: uw_write(ctx, "Content-type: text/plain\r\n\r\n"); adamc@853: uw_write(ctx, "Error running SQL ROLLBACK\n"); adamc@853: } adamc@853: adamc@853: return r; adamc@853: } adamc@853: adamc@856: uw_context uw_request_new_context(void *logger_data, uw_logger log_error, uw_logger log_debug) { adamc@853: uw_context ctx = uw_init(); adamc@853: int retries_left = MAX_RETRIES; adamc@853: adamc@853: while (1) { adamc@853: failure_kind fk = uw_begin_init(ctx); adamc@853: adamc@853: if (fk == SUCCESS) { adamc@856: log_debug(logger_data, "Database connection initialized.\n"); adamc@853: break; adamc@853: } else if (fk == BOUNDED_RETRY) { adamc@853: if (retries_left) { adamc@856: log_debug(logger_data, "Initialization error triggers bounded retry: %s\n", uw_error_message(ctx)); adamc@853: --retries_left; adamc@853: } else { adamc@856: log_error(logger_data, "Fatal initialization error (out of retries): %s\n", uw_error_message(ctx)); adamc@853: uw_free(ctx); adamc@853: return NULL; adamc@853: } adamc@853: } else if (fk == UNLIMITED_RETRY) adamc@856: log_debug(logger_data, "Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx)); adamc@853: else if (fk == FATAL) { adamc@856: log_error(logger_data, "Fatal initialization error: %s\n", uw_error_message(ctx)); adamc@853: uw_free(ctx); adamc@853: return NULL; adamc@853: } else { adamc@856: log_error(logger_data, "Unknown uw_begin_init return code!\n"); adamc@853: uw_free(ctx); adamc@853: return NULL; adamc@853: } adamc@853: } adamc@853: adamc@853: return ctx; adamc@853: } adamc@853: adamc@853: #define KEYSIZE 16 adamc@853: #define PASSSIZE 4 adamc@853: adamc@853: #define HASH_ALGORITHM MHASH_SHA256 adamc@853: #define HASH_BLOCKSIZE 32 adamc@853: #define KEYGEN_ALGORITHM KEYGEN_MCRYPT adamc@853: adamc@853: int uw_hash_blocksize = HASH_BLOCKSIZE; adamc@853: adamc@853: static int password[PASSSIZE]; adamc@853: static unsigned char private_key[KEYSIZE]; adamc@853: adamc@856: static void init_crypto(void *logger_data, uw_logger log_error) { adamc@853: KEYGEN kg = {{HASH_ALGORITHM, HASH_ALGORITHM}}; adamc@853: int i; adamc@853: adamc@853: assert(mhash_get_block_size(HASH_ALGORITHM) == HASH_BLOCKSIZE); adamc@853: adamc@853: for (i = 0; i < PASSSIZE; ++i) adamc@853: password[i] = rand(); adamc@853: adamc@853: if (mhash_keygen_ext(KEYGEN_ALGORITHM, kg, adamc@853: private_key, sizeof(private_key), adamc@853: (unsigned char*)password, sizeof(password)) < 0) { adamc@856: log_error(logger_data, "Key generation failed\n"); adamc@853: exit(1); adamc@853: } adamc@853: } adamc@853: adamc@856: void uw_request_init(void *logger_data, uw_logger log_error, uw_logger log_debug) { adamc@853: uw_context ctx; adamc@853: failure_kind fk; adamc@853: adamc@853: uw_global_init(); adamc@853: adamc@856: ctx = uw_request_new_context(logger_data, log_error, log_debug); adamc@853: adamc@853: if (!ctx) adamc@853: exit(1); adamc@853: adamc@853: for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) { adamc@856: log_debug(logger_data, "Unlimited retry during init: %s\n", uw_error_message(ctx)); adamc@853: uw_db_rollback(ctx); adamc@853: uw_reset(ctx); adamc@853: } adamc@853: adamc@853: if (fk != SUCCESS) { adamc@856: log_error(logger_data, "Failed to initialize database! %s\n", uw_error_message(ctx)); adamc@853: uw_db_rollback(ctx); adamc@853: exit(1); adamc@853: } adamc@853: adamc@853: uw_free(ctx); adamc@853: adamc@856: init_crypto(logger_data, log_error); adamc@853: } adamc@853: adamc@853: void uw_sign(const char *in, char *out) { adamc@853: MHASH td; adamc@853: adamc@853: td = mhash_hmac_init(HASH_ALGORITHM, private_key, sizeof(private_key), adamc@853: mhash_get_hash_pblock(HASH_ALGORITHM)); adamc@853: adamc@853: mhash(td, in, strlen(in)); adamc@853: if (mhash_hmac_deinit(td, out) < 0) adamc@856: fprintf(stderr, "Signing failed\n"); adamc@853: } adamc@853: adamc@853: typedef struct uw_rc { adamc@853: size_t path_copy_size; adamc@853: char *path_copy; adamc@853: } *uw_request_context; adamc@853: adamc@853: uw_request_context uw_new_request_context(void) { adamc@853: uw_request_context r = malloc(sizeof(struct uw_rc)); adamc@853: r->path_copy_size = 0; adamc@853: r->path_copy = malloc(0); adamc@853: return r; adamc@853: } adamc@853: adamc@853: void uw_free_request_context(uw_request_context r) { adamc@853: free(r->path_copy); adamc@853: free(r); adamc@853: } adamc@853: adamc@863: extern char *uw_url_prefix; adamc@863: adamc@854: request_result uw_request(uw_request_context rc, uw_context ctx, adamc@854: char *method, char *path, char *query_string, adamc@854: char *body, size_t body_len, adamc@856: void (*on_success)(uw_context), void (*on_failure)(uw_context), adamc@856: void *logger_data, uw_logger log_error, uw_logger log_debug, adamc@863: int sock, adamc@863: int (*send)(int sockfd, const void *buf, size_t len), adamc@863: int (*close)(int fd)) { adamc@853: int retries_left = MAX_RETRIES; adamc@853: char *s; adamc@853: failure_kind fk; adamc@853: int is_post = 0, do_normal_send = 1; adamc@853: char *boundary = NULL; adamc@853: size_t boundary_len; adamc@854: char *inputs; adamc@853: adamc@854: if (!strcmp(method, "POST")) { adamc@853: char *clen_s = uw_Basis_requestHeader(ctx, "Content-length"); adamc@853: if (!clen_s) { adamc@1037: clen_s = "0"; adamc@1037: /*log_error(logger_data, "No Content-length with POST\n"); adamc@1037: return FAILED;*/ adamc@853: } adamc@853: int clen = atoi(clen_s); adamc@853: if (clen < 0) { adamc@856: log_error(logger_data, "Negative Content-length with POST\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@854: if (body_len < clen) { adamc@856: log_error(logger_data, "Request doesn't contain all POST data (according to Content-Length)\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: is_post = 1; adamc@853: adamc@853: clen_s = uw_Basis_requestHeader(ctx, "Content-type"); adamc@853: if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) { adamc@853: if (strncasecmp(clen_s + 19, "; boundary=", 11)) { adamc@856: log_error(logger_data, "Bad multipart boundary spec"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: boundary = clen_s + 28; adamc@853: boundary[0] = '-'; adamc@853: boundary[1] = '-'; adamc@853: boundary_len = strlen(boundary); adamc@853: } adamc@854: } else if (strcmp(method, "GET")) { adamc@856: log_error(logger_data, "Not ready for non-GET/POST command: %s\n", method); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@863: if (!strncmp(path, uw_url_prefix, strlen(uw_url_prefix)) adamc@863: && !strcmp(path + strlen(uw_url_prefix), ".msgs")) { adamc@853: char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client"); adamc@853: char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass"); adamc@853: adamc@853: if (sock < 0) { adamc@856: log_error(logger_data, ".msgs requested, but not socket supplied\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: if (id && pass) { adamc@853: unsigned idn = atoi(id); adamc@864: uw_client_connect(idn, atoi(pass), sock, send, close, logger_data, log_error); adamc@856: log_error(logger_data, "Processed request for messages by client %u\n\n", idn); adamc@853: return KEEP_OPEN; adamc@853: } adamc@853: else { adamc@856: log_error(logger_data, "Missing fields in .msgs request: %s, %s\n\n", id, pass); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: adamc@853: if (boundary) { adamc@854: char *part = body, *after_sub_headers, *header, *after_header; adamc@853: size_t part_len; adamc@853: adamc@853: part = strstr(part, boundary); adamc@853: if (!part) { adamc@856: log_error(logger_data, "Missing first multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: part += boundary_len; adamc@853: adamc@853: while (1) { adamc@853: char *name = NULL, *filename = NULL, *type = NULL; adamc@853: adamc@853: if (part[0] == '-' && part[1] == '-') adamc@853: break; adamc@853: adamc@853: if (*part != '\r') { adamc@856: log_error(logger_data, "No \\r after multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: ++part; adamc@853: if (*part != '\n') { adamc@856: log_error(logger_data, "No \\n after multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: ++part; adamc@853: adamc@853: if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) { adamc@856: log_error(logger_data, "Missing end of headers after multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: after_sub_headers[2] = 0; adamc@853: after_sub_headers += 4; adamc@853: adamc@853: for (header = part; after_header = strstr(header, "\r\n"); header = after_header + 2) { adamc@853: char *colon, *after_colon; adamc@853: adamc@853: *after_header = 0; adamc@853: if (!(colon = strchr(header, ':'))) { adamc@856: log_error(logger_data, "Missing colon in multipart sub-header\n"); adamc@853: return FAILED; adamc@853: } adamc@853: *colon++ = 0; adamc@853: if (*colon++ != ' ') { adamc@856: log_error(logger_data, "No space after colon in multipart sub-header\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: if (!strcasecmp(header, "Content-Disposition")) { adamc@853: if (strncmp(colon, "form-data; ", 11)) { adamc@856: log_error(logger_data, "Multipart data is not \"form-data\"\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: for (colon += 11; after_colon = strchr(colon, '='); colon = after_colon) { adamc@853: char *data; adamc@853: after_colon[0] = 0; adamc@853: if (after_colon[1] != '"') { adamc@856: log_error(logger_data, "Disposition setting is missing initial quote\n"); adamc@853: return FAILED; adamc@853: } adamc@853: data = after_colon+2; adamc@853: if (!(after_colon = strchr(data, '"'))) { adamc@856: log_error(logger_data, "Disposition setting is missing final quote\n"); adamc@853: return FAILED; adamc@853: } adamc@853: after_colon[0] = 0; adamc@853: ++after_colon; adamc@853: if (after_colon[0] == ';' && after_colon[1] == ' ') adamc@853: after_colon += 2; adamc@853: adamc@853: if (!strcasecmp(colon, "name")) adamc@853: name = data; adamc@853: else if (!strcasecmp(colon, "filename")) adamc@853: filename = data; adamc@853: } adamc@853: } else if (!strcasecmp(header, "Content-Type")) { adamc@853: type = colon; adamc@853: } adamc@853: } adamc@853: adamc@854: part = memmem(after_sub_headers, body + body_len - after_sub_headers, boundary, boundary_len); adamc@853: if (!part) { adamc@856: log_error(logger_data, "Missing boundary after multipart payload\n"); adamc@853: return FAILED; adamc@853: } adamc@853: part[-2] = 0; adamc@853: part_len = part - after_sub_headers - 2; adamc@853: part[0] = 0; adamc@853: part += boundary_len; adamc@853: adamc@853: if (filename) { adamc@853: uw_Basis_file f = {filename, type, {part_len, after_sub_headers}}; adamc@853: adamc@853: if (uw_set_file_input(ctx, name, f)) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } else if (uw_set_input(ctx, name, after_sub_headers)) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: } adamc@853: else { adamc@854: inputs = is_post ? body : query_string; adamc@853: adamc@853: if (inputs) { adamc@853: char *name, *value; adamc@853: adamc@853: while (*inputs) { adamc@853: name = inputs; adamc@853: if (inputs = strchr(inputs, '&')) adamc@853: *inputs++ = 0; adamc@853: else adamc@853: inputs = strchr(name, 0); adamc@853: adamc@853: if (value = strchr(name, '=')) { adamc@853: *value++ = 0; adamc@853: if (uw_set_input(ctx, name, value)) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: else if (uw_set_input(ctx, name, "")) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: } adamc@853: } adamc@853: adamc@856: log_debug(logger_data, "Serving URI %s....\n", path); adamc@853: adamc@853: while (1) { adamc@853: size_t path_len = strlen(path); adamc@853: adamc@856: on_success(ctx); adamc@853: adamc@853: if (path_len + 1 > rc->path_copy_size) { adamc@853: rc->path_copy_size = path_len + 1; adamc@853: rc->path_copy = realloc(rc->path_copy, rc->path_copy_size); adamc@853: } adamc@853: strcpy(rc->path_copy, path); adamc@853: fk = uw_begin(ctx, rc->path_copy); adamc@1065: if (fk == SUCCESS || fk == RETURN_INDIRECTLY) { adamc@853: uw_commit(ctx); adamc@853: return SERVED; adamc@853: } else if (fk == BOUNDED_RETRY) { adamc@853: if (retries_left) { adamc@856: log_debug(logger_data, "Error triggers bounded retry: %s\n", uw_error_message(ctx)); adamc@853: --retries_left; adamc@853: } adamc@853: else { adamc@856: log_error(logger_data, "Fatal error (out of retries): %s\n", uw_error_message(ctx)); adamc@853: adamc@856: try_rollback(ctx, logger_data, log_error); adamc@853: adamc@853: uw_reset_keep_error_message(ctx); adamc@856: on_failure(ctx); adamc@853: uw_write_header(ctx, "Content-type: text/plain\r\n"); adamc@853: uw_write(ctx, "Fatal error (out of retries): "); adamc@853: uw_write(ctx, uw_error_message(ctx)); adamc@853: uw_write(ctx, "\n"); adamc@853: adamc@853: return FAILED; adamc@853: } adamc@853: } else if (fk == UNLIMITED_RETRY) adamc@856: log_debug(logger_data, "Error triggers unlimited retry: %s\n", uw_error_message(ctx)); adamc@853: else if (fk == FATAL) { adamc@856: log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx)); adamc@853: adamc@856: try_rollback(ctx, logger_data, log_error); adamc@853: adamc@853: uw_reset_keep_error_message(ctx); adamc@856: on_failure(ctx); adamc@853: uw_write_header(ctx, "Content-type: text/html\r\n"); adamc@853: uw_write(ctx, "Fatal Error"); adamc@853: uw_write(ctx, "Fatal error: "); adamc@853: uw_write(ctx, uw_error_message(ctx)); adamc@853: uw_write(ctx, "\n"); adamc@853: adamc@853: return FAILED; adamc@853: } else { adamc@856: log_error(logger_data, "Unknown uw_handle return code!\n"); adamc@853: adamc@856: try_rollback(ctx, logger_data, log_error); adamc@853: adamc@853: uw_reset_keep_request(ctx); adamc@856: on_failure(ctx); adamc@853: uw_write_header(ctx, "Content-type: text/plain\r\n"); adamc@853: uw_write(ctx, "Unknown uw_handle return code!\n"); adamc@853: adamc@853: return FAILED; adamc@853: } adamc@853: adamc@856: if (try_rollback(ctx, logger_data, log_error)) adamc@853: return FAILED; adamc@853: adamc@853: uw_reset_keep_request(ctx); adamc@853: } adamc@853: } adamc@853: adamc@856: typedef struct { adamc@856: void *logger_data; adamc@856: uw_logger log_error, log_debug; adamc@856: } loggers; adamc@856: adamc@853: void *client_pruner(void *data) { adamc@856: loggers *ls = (loggers *)data; adamc@856: uw_context ctx = uw_request_new_context(ls->logger_data, ls->log_error, ls->log_debug); adamc@853: adamc@853: if (!ctx) adamc@853: exit(1); adamc@853: adamc@853: while (1) { adamc@853: uw_prune_clients(ctx); adamc@853: sleep(5); adamc@853: } adamc@853: }