diff --git a/src/lib/http.c b/src/lib/http.c index 98d8ef2..2b8417f 100644 --- a/src/lib/http.c +++ b/src/lib/http.c @@ -146,6 +146,9 @@ int rfc3161_handler(struct mg_connection *conn, void *context) { bool is_tsq = 0; + // go through every headers to find Content-Type + // and check if it's set to "application/timestamp-query" + // if it's the case, set is_tsq (is time-stamp query) to True for (int i = 0; i < request_info->num_headers; i++) { const char *h_name = request_info->http_headers[i].name; const char *h_value = request_info->http_headers[i].value; @@ -159,13 +162,22 @@ int rfc3161_handler(struct mg_connection *conn, void *context) { char *serial_id = NULL; - // Send HTTP reply to the client + // Send HTTP reply to the client. + // + // If it's a time-stamp query. if (is_tsq) { + // Recover query content from http request. char *query = calloc(request_info->content_length, sizeof(char)); int query_len = mg_read(conn, query, request_info->content_length); + // Log the query as DEBUG log in hexadecimal format log_hex(ct, LOG_DEBUG, "query hexdump content", (unsigned char *)query, request_info->content_length); + // Get an OpenSSL TS_RESP_CTX (wrapped inside ts_resp_ctx_wrapper + // structure). + // get_ctxw recovers the first unused TS_RESP_CTX + // in the ct->ts_ctx_pool pool of TS_RESP_CTX. + // (TS_RESP_CTX are not thread safe) ts_resp_ctx_wrapper *ctx_w = get_ctxw(ct); if (ctx_w == NULL) { resp_code = 500; @@ -173,56 +185,60 @@ int rfc3161_handler(struct mg_connection *conn, void *context) { "Unable to get an OpenSSL ts_context in the pool"); } else { + // create the response resp_code = create_response(ct, query, query_len, ctx_w->ts_ctx, &content_length, &content, &serial_id); + // free the TS_RESP_CTX used ctx_w->available = 1; } + // respond according to create_response return code switch (resp_code) { case 200: - mg_printf(conn, - "HTTP/1.1 200 OK\r\n" - "Content-Type: application/timestamp-reply\r\n" - "Content-Length: %d\r\n" // Always set Content-Length - "\r\n", + mg_printf(conn, "HTTP/1.1 200 OK\r\n" + "Content-Type: application/timestamp-reply\r\n" + "Content-Length: %d\r\n" + "\r\n", (int)content_length); mg_write(conn, content, content_length); log_hex(ct, LOG_DEBUG, "response hexdump content", content, content_length); break; case 400: - mg_printf(conn, - "HTTP/1.1 400 Bad Request\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: 12\r\n" // Always set Content-Length - "\r\n" - "client error"); + mg_printf(conn, "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 12\r\n" + "\r\n" + "client error"); break; default: - mg_printf(conn, - "HTTP/1.1 500 Internal Server Error\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: 17\r\n" // Always set Content-Length - "\r\n" - "uts-server error"); + mg_printf(conn, "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 17\r\n" + "\r\n" + "uts-server error"); } free(query); free(content); } else { + // default reply if we don't have a time-stamp request resp_code = 200; - mg_printf(conn, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: 46\r\n" // Always set Content-Length - "\r\n" - "uts-server, a simple RFC 3161 timestamp server"); + mg_printf(conn, "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 46\r\n" + "\r\n" + "uts-server, a simple RFC 3161 timestamp server"); } + // initialize a serial_id if not created by create_response if (serial_id == NULL) { serial_id = calloc(9, sizeof(char)); serial_id = rand_string(serial_id, 8); } + // some debugging logs log_request_debug(request_info, serial_id, ct); + // end of some timer stuff diff = clock() - start; + // log the request log_request(request_info, serial_id, ct, resp_code, (diff * 1000000 / CLOCKS_PER_SEC)); free(serial_id); diff --git a/src/lib/rfc3161.c b/src/lib/rfc3161.c index 2c302e8..6f30dd0 100644 --- a/src/lib/rfc3161.c +++ b/src/lib/rfc3161.c @@ -66,6 +66,7 @@ int add_oid_section(rfc3161_context *ct, CONF *conf) { return 1; } +// initialize OpenSSL global black magic... void init_ssl() { SSL_load_error_strings(); ERR_load_BIO_strings(); @@ -73,6 +74,7 @@ void init_ssl() { ERR_load_TS_strings(); } +// free OpenSSL global black magic... void free_ssl() { CONF_modules_unload(1); EVP_cleanup(); @@ -84,28 +86,41 @@ void free_ssl() { OBJ_cleanup(); } -// recover a ts wrapper +// Recover a ts wrapper from a pool of TS_RESP_CTX. +// Used because TS_RESP_CTX is not thread safe. ts_resp_ctx_wrapper *get_ctxw(rfc3161_context *ct) { ts_resp_ctx_wrapper *ret = NULL; + // itarate on the 'numthreads' TS_RESP_CTX we have in the pool + // we have as much as TS_RESP_CTX as parallele handlers for (int i = 0; i < ct->numthreads; i++) { + // wait until we have exclusive access to read and maybe + // write the ts_resp_ctx_wrapper structure. pthread_mutex_lock(&ct->ts_ctx_pool[i].lock); + // if the TS_RESP_CTX is available, + // take it and mark it as reserved (available = 0) if (ct->ts_ctx_pool[i].available) { ct->ts_ctx_pool[i].available = 0; ret = &(ct->ts_ctx_pool[i]); + // unlock the the ts_resp_ctx_wrapper pthread_mutex_unlock(&ct->ts_ctx_pool[i].lock); + // return the ts_resp_ctx_wrapper return ret; } + // unlock in case the ts_resp_ctx_wrapper was not available pthread_mutex_unlock(&ct->ts_ctx_pool[i].lock); } + // default return if no TS_RESP_CTX wa available return ret; } +// create a TS_RESP_CTX (OpenSSL Time-Stamp Response Context) TS_RESP_CTX *create_tsctx(rfc3161_context *ct, CONF *conf, const char *section, const char *policy) { unsigned long err_code; unsigned long err_code_prev = 0; TS_RESP_CTX *resp_ctx = NULL; + // recover the section defining the default tsa if ((section = TS_CONF_get_tsa_section(conf, section)) == NULL) { uts_logger(ct, LOG_ERR, "failed to get or use '%s' in section [ %s ]", "default_tsa", "tsa"); @@ -115,6 +130,7 @@ TS_RESP_CTX *create_tsctx(rfc3161_context *ct, CONF *conf, const char *section, uts_logger(ct, LOG_ERR, "failed to initialize tsa context"); goto end; } + // recover and set various parameters TS_RESP_CTX_set_serial_cb(resp_ctx, serial_cb, NULL); if (!TS_CONF_set_crypto_device(conf, section, NULL)) { uts_logger(ct, LOG_ERR, "failed to get or use '%s' in section [ %s ]", @@ -189,6 +205,7 @@ TS_RESP_CTX *create_tsctx(rfc3161_context *ct, CONF *conf, const char *section, } return resp_ctx; end: + // log the OpenSSL errors if any in case of error while ((err_code = ERR_get_error())) { if (err_code_prev != err_code) { uts_logger(ct, LOG_DEBUG, "OpenSSL exception: '%s'", @@ -199,6 +216,7 @@ end: } err_code_prev = err_code; } + // free the TS_RESP_CTX if initialization has faile TS_RESP_CTX_free(resp_ctx); return NULL; } @@ -215,10 +233,13 @@ int create_response(rfc3161_context *ct, char *query, int query_len, unsigned long err_code; unsigned long err_code_prev = 0; + // create the input bio for OpenSSL containing the query if ((query_bio = BIO_new_mem_buf(query, query_len)) == NULL) { uts_logger(ct, LOG_ERR, "failed to parse query"); goto end; } + + // generate the response if ((ts_response = TS_RESP_create_response(resp_ctx, query_bio)) == NULL) { uts_logger(ct, LOG_ERR, "failed to create ts response"); goto end; @@ -252,6 +273,7 @@ end: serial_hex = calloc(SERIAL_ID_SIZE, sizeof(char)); strncpy(serial_hex, " NO ID ", SERIAL_ID_SIZE + 2); } + // get a short version of the serial (150 bits in hexa is a bit long) strncpy(*serial_id, serial_hex, SERIAL_ID_SIZE); @@ -271,6 +293,7 @@ end: bptr->data, *serial_id); // emit logs according the return value + // and set the return code long status = ASN1_INTEGER_get(ts_response->status_info->status); switch (status) { case TS_STATUS_GRANTED: @@ -312,7 +335,7 @@ end: ret = 500; } - // log the openssl errors + // log the OpenSSL errors if any while ((err_code = ERR_get_error())) { if (err_code_prev != err_code) { ERR_load_TS_strings(); @@ -331,6 +354,9 @@ end: return ret; } +// Build a random serial for each request. +// It's less painful to manage than an incremental serial stored in a file +// and a 150 bits size is more than enough to prevent collision. static ASN1_INTEGER *serial_cb(TS_RESP_CTX *ctx, void *data42) { unsigned char data[150] = {0}; RAND_bytes(data, sizeof(data)); diff --git a/src/lib/utils.c b/src/lib/utils.c index e1a56f6..41b3288 100644 --- a/src/lib/utils.c +++ b/src/lib/utils.c @@ -136,6 +136,7 @@ void skeleton_daemon() { // openlog("uts-server", LOG_PID, LOG_DAEMON); } +// log a binary blob as hexadecimal void log_hex(rfc3161_context *ct, int priority, char *id, unsigned char *content, int content_length) { if (priority > ct->loglevel && !ct->stdout_dbg) @@ -154,22 +155,26 @@ void log_hex(rfc3161_context *ct, int priority, char *id, free(out); } +// logger function void uts_logger(rfc3161_context *ct, int priority, char *fmt, ...) { // ignore all messages less critical than the loglevel // except if the debug flag is set if (priority > ct->loglevel && !ct->stdout_dbg) return; + + // build the out log message FILE *stream; char *out; size_t len; stream = open_memstream(&out, &len); va_list args; - va_start(args, fmt); vfprintf(stream, fmt, args); va_end(args); fflush(stream); fclose(stream); + + // if in debugging mode, also log to stdout if (ct->stdout_dbg) { switch (priority) { case LOG_EMERG: @@ -209,6 +214,7 @@ void uts_logger(rfc3161_context *ct, int priority, char *fmt, ...) { free(out); } +// OpenSSL file openner (use for opening the configuration file static BIO *bio_open_default(rfc3161_context *ct, const char *filename, int format) { BIO *ret; @@ -229,6 +235,7 @@ static BIO *bio_open_default(rfc3161_context *ct, const char *filename, return NULL; } +// loading the configuration file and parsing it using the OpenSSL parser static CONF *load_config_file(rfc3161_context *ct, const char *filename) { long errorline = -1; BIO *in; @@ -260,13 +267,18 @@ static CONF *load_config_file(rfc3161_context *ct, const char *filename) { return NULL; } +// initialize the rfc3161_context according to the conf_file content int set_params(rfc3161_context *ct, char *conf_file, char *conf_wd) { + // chdir in configuration file directory + // (some parameters like certificates can be declared + // relatively to the configuration file). chdir(conf_wd); int ret = 1; int http_counter = 0; int numthreads = 42; NCONF_free(ct->conf); + // load the configuration file ct->conf = load_config_file(ct, conf_file); if (ct->conf == NULL) goto end; @@ -297,6 +309,7 @@ int set_params(rfc3161_context *ct, char *conf_file, char *conf_wd) { ; } } + // parse the options to get the civetweb options and a few other things for (int i = 0; i < RFC3161_OPTIONS_LEN; i++) { int type = rfc3161_options[i].type; const char *name = rfc3161_options[i].name; @@ -311,6 +324,8 @@ int set_params(rfc3161_context *ct, char *conf_file, char *conf_wd) { uts_logger(ct, LOG_DEBUG, "configuration param['%s'] = '%s'", name, value); switch (type) { + // if it's an http (civetweb) option, put it in the http_options buffer + // like civetweb is expected it. case HTTP_OPTIONS: if (value != NULL) { ct->http_options[http_counter] = name; @@ -318,6 +333,8 @@ int set_params(rfc3161_context *ct, char *conf_file, char *conf_wd) { ct->http_options[http_counter] = value; http_counter++; } + // recover the num_threads parameter as it's used to + // initialize the TS_RESP_CTX pool if (strcmp(name, "num_threads") == 0) numthreads = atoi(value); break; @@ -331,6 +348,9 @@ int set_params(rfc3161_context *ct, char *conf_file, char *conf_wd) { if (!add_oid_section(ct, ct->conf)) ret = 0; + // initialize the TS_RESP_CTX pool + // as TS_RESP_CTX is not thread safe, + // creates 'num_threads' TS_RESP_CTX (one per thread) ct->ts_ctx_pool = calloc(numthreads, sizeof(ts_resp_ctx_wrapper)); ct->numthreads = numthreads; for (int i = 0; i < numthreads; i++) { @@ -339,6 +359,7 @@ int set_params(rfc3161_context *ct, char *conf_file, char *conf_wd) { if (ct->ts_ctx_pool[i].ts_ctx == NULL) ret = 0; } + // like any good daemon, return to '/' once the configuration is loaded chdir("/"); return ret;