From 93756a84eaf31579050da7d9d7f902ee367d6535 Mon Sep 17 00:00:00 2001 From: Wesley Miaw Date: Mon, 7 Oct 2019 14:38:16 -0700 Subject: [PATCH] Reject HTTP requests with an invalid (e.g. negative) Content-Length header value. Make sure to allocate enough memory in buffers for the operations they are used for, and to check/enforce buffer sizes when performing those memory operations. Properly allocate and free memory. Make a best effort at allocating memory for the network hardware address (remove code for Apple platforms). Try to consume all of the remaining content if a valid Content-Length header was provided. Check for success when attempting to acquire mutexes. --- server/dial_data.c | 59 ++++++-- server/dial_server.c | 295 ++++++++++++++++++++++++++------------ server/dial_server.h | 2 +- server/main.c | 67 +++++---- server/mongoose.c | 192 +++++++++++++++---------- server/nf_callbacks.c | 34 +++-- server/quick_ssdp.c | 105 +++++++------- server/system_callbacks.c | 4 +- server/url_lib.c | 95 +++++++++--- server/url_lib.h | 2 +- 10 files changed, 555 insertions(+), 300 deletions(-) diff --git a/server/dial_data.c b/server/dial_data.c index 2a8c6c3..ac62527 100644 --- a/server/dial_data.c +++ b/server/dial_data.c @@ -44,46 +44,79 @@ void set_dial_data_dir(const char *data_dir) { static char* getAppPath(char *app_name) { size_t name_size = strlen(app_name) + sizeof(dial_data_dir) + 1; char* filename = (char*) malloc(name_size); + if (filename == NULL) { + return NULL; + } filename[0] = 0; strncat(filename, dial_data_dir, name_size); strncat(filename, app_name, name_size - sizeof(dial_data_dir)); return filename; } -void store_dial_data(char app_name[], DIALData *data) { +void store_dial_data(char *app_name, DIALData *data) { char* filename = getAppPath(app_name); + if (filename == NULL) { + printf("Cannot open DIAL data output file, out-of-memory."); + exit(1); + } FILE *f = fopen(filename, "w"); + free(filename); filename = NULL; if (f == NULL) { printf("Cannot open DIAL data output file: %s\n", filename); exit(1); } - free(filename); for (DIALData *first = data; first != NULL; first = first->next) { - fprintf(f, "%s %s\n", first->key, first->value); + // truncate because we have limits on length when retrieving. + fprintf(f, "%.*s %.*s\n", DIAL_KEY_OR_VALUE_MAX_LEN, first->key, DIAL_KEY_OR_VALUE_MAX_LEN, first->value); } fclose(f); } DIALData *retrieve_dial_data(char *app_name) { char* filename = getAppPath(app_name); + if (filename == NULL) { + return NULL; // no dial data found, that's fine + } FILE *f = fopen(filename, "r"); - free(filename); + free(filename); filename = NULL; if (f == NULL) { return NULL; // no dial data found, that's fine } DIALData *result = NULL; - char key[256]; - char value[256]; - while (fscanf(f, "%255s %255s\n", key, value) != EOF) { + char key[DIAL_KEY_OR_VALUE_MAX_LEN + 1] = {0,}; + char value[DIAL_KEY_OR_VALUE_MAX_LEN + 1] = {0,}; + int err = 0; + while (fscanf(f, "%" DIAL_KEY_OR_VALUE_MAX_LEN_STR "s %" DIAL_KEY_OR_VALUE_MAX_LEN_STR "s\n", key, value) != EOF) { DIALData *newNode = (DIALData *) malloc(sizeof(DIALData)); - newNode->key = (char *) calloc(1, strlen(key)); + if (newNode == NULL) { + err = 1; + break; + } + + newNode->key = (char *) calloc(strlen(key) + 1, sizeof(char)); + if (newNode->key == NULL) { + err = 1; + free(newNode); newNode = NULL; + break; + } strncpy(newNode->key, key, strlen(key)); - newNode->value = (char *) calloc(1, strlen(value)); + + newNode->value = (char *) calloc(strlen(value) + 1, sizeof(char)); + if (newNode->value == NULL) { + err = 1; + free(newNode->key); newNode->key = NULL; + free(newNode); newNode = NULL; + break; + } strncpy(newNode->value, value, strlen(value)); newNode->next = result; result = newNode; } fclose(f); + if (err) { + free_dial_data(&result); + result = NULL; + } return result; } @@ -92,10 +125,10 @@ void free_dial_data(DIALData **dialData) DIALData *curNode=NULL; while (*dialData != NULL) { curNode = *dialData; - *dialData =curNode->next; + *dialData = curNode->next; - free(curNode->key); - free(curNode->value); - free(curNode); + free(curNode->key); curNode->key = NULL; + free(curNode->value); curNode->value = NULL; + free(curNode); curNode = NULL; } } diff --git a/server/dial_server.c b/server/dial_server.c index c8fd7a8..16d62e6 100644 --- a/server/dial_server.c +++ b/server/dial_server.c @@ -66,12 +66,32 @@ struct DIALServer_ { pthread_mutex_t mux; }; -static void ds_lock(DIALServer *ds) { - pthread_mutex_lock(&ds->mux); +/** + * Acquire the DIAL server mutex. + * + * @return 1 if acquisition succeeded, 0 if it failed. + */ +static int ds_lock(DIALServer *ds) { + int err = 0; + if ((err = pthread_mutex_lock(&ds->mux)) != 0) { + printf("Unable to acquire DS mutex: [%d]", err); + return 0; + } + return 1; } -static void ds_unlock(DIALServer *ds) { - pthread_mutex_unlock(&ds->mux); +/** + * Release the DIAL server mutex. + * + * @return 1 if release succeeded, 0 if it failed. + */ +static int ds_unlock(DIALServer *ds) { + int err = 0; + if ((err = pthread_mutex_unlock(&ds->mux)) != 0) { + printf("Unable to release DS mutex: [%d]", err); + return 0; + } + return 1; } // finds an app and returns a pointer to the previous element's next pointer @@ -88,11 +108,25 @@ static DIALApp **find_app(DIALServer *ds, const char *app_name) { return ret; } -static void url_decode_xml_encode(char *dst, char *src, size_t src_size) { +/** + * URL-unescape the string, then XML-escape it. + * + * @param dst the destination XML-escaped string. Must be large enough for + * the resulting string (technically as much as 6x the raw string + * length based on the implementation in url_lib.c). + * @param src the URL-escaped string. + * @param src_size size of the URL-escaped string, including trailing NULL. + * @return true if successful, false if out-of-memory. + */ +static int url_decode_xml_encode(char *dst, char *src, size_t src_size) { char *url_decoded_key = (char *) malloc(src_size + 1); + if (url_decoded_key == NULL) { + return 0; + } urldecode(url_decoded_key, src, src_size); xmlencode(dst, url_decoded_key, 2 * src_size); free(url_decoded_key); + return 1; } /* @@ -117,19 +151,21 @@ static void handle_app_start(struct mg_connection *conn, const struct mg_request_info *request_info, const char *app_name, const char *origin_header) { - char additional_data_param[256] = {0, }; + char additional_data_param[DIAL_MAX_ADDITIONALURL] = {0, }; char body[DIAL_MAX_PAYLOAD + sizeof(additional_data_param) + 2] = {0, }; DIALApp *app; DIALServer *ds = request_info->user_data; int body_size; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); if (!app) { mg_send_http_error(conn, 404, "Not Found", "Not Found"); } else { - body_size = mg_read(conn, body, sizeof(body)); - // NUL-terminate it just in case + body_size = mg_read(conn, body, sizeof(body) - 1); if (body_size > DIAL_MAX_PAYLOAD) { mg_send_http_error(conn, 413, "413 Request Entity Too Large", "413 Request Entity Too Large"); @@ -144,7 +180,7 @@ static void handle_app_start(struct mg_connection *conn, if (app->useAdditionalData) { // Construct additionalDataUrl=http://host:port/apps/app_name/dial_data - sprintf(additional_data_param, + snprintf(additional_data_param, DIAL_MAX_ADDITIONALURL, "additionalDataUrl=http%%3A%%2F%%2Flocalhost%%3A%d%%2Fapps%%2F%s%%2Fdial_data%%3F", dial_port, app_name); } @@ -170,6 +206,8 @@ static void handle_app_start(struct mg_connection *conn, mg_send_http_error(conn, 403, "Forbidden", "Forbidden"); } else if (app->state == kDIALStatusErrorUnauth) { mg_send_http_error(conn, 401, "Unauthorized", "Unauthorized"); + } else if (app->state == kDIALStatusErrorNotImplemented) { + mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); } else { mg_send_http_error(conn, 503, "Service Unavailable", "Service Unavailable"); @@ -195,7 +233,10 @@ static void handle_app_status(struct mg_connection *conn, free(clientVersionStr); } - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); if (!app) { mg_send_http_error(conn, 404, "Not Found", "Not Found"); @@ -208,23 +249,45 @@ static void handle_app_status(struct mg_connection *conn, char *p = dial_data; for (DIALData* first = app->dial_data; first != NULL; first = first->next) { - p = smartstrcat(p, " <", end - p); + p = smartstrncpy(p, " <", end - p); size_t key_length = strlen(first->key); char *encoded_key = (char *) malloc(2 * key_length + 1); - url_decode_xml_encode(encoded_key, first->key, key_length); + if (encoded_key == NULL) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + ds_unlock(ds); + return; + } + if (!url_decode_xml_encode(encoded_key, first->key, key_length)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + free(encoded_key); encoded_key = NULL; + ds_unlock(ds); + return; + }; size_t value_length = strlen(first->value); char *encoded_value = (char *) malloc(2 * value_length + 1); - url_decode_xml_encode(encoded_value, first->value, value_length); + if (encoded_value == NULL) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + free(encoded_key); encoded_key = NULL; + ds_unlock(ds); + return; + } + if (!url_decode_xml_encode(encoded_value, first->value, value_length)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + free(encoded_value); encoded_value = NULL; + free(encoded_key); encoded_key = NULL; + ds_unlock(ds); + return; + } - p = smartstrcat(p, encoded_key, end - p); - p = smartstrcat(p, ">", end - p); - p = smartstrcat(p, encoded_value, end - p); - p = smartstrcat(p, "", end - p); - free(encoded_key); - free(encoded_value); + p = smartstrncpy(p, encoded_key, end - p); + p = smartstrncpy(p, ">", end - p); + p = smartstrncpy(p, encoded_value, end - p); + p = smartstrncpy(p, "", end - p); + free(encoded_key); encoded_key = NULL; + free(encoded_value); encoded_value = NULL; } app->state = app->callbacks.status_cb(ds, app_name, app->run_id, &canStop, @@ -284,7 +347,10 @@ static void handle_app_stop(struct mg_connection *conn, DIALServer *ds = request_info->user_data; int canStop = 0; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } // Special handling for system app if (strcmp(app_name, "system") == 0) { @@ -321,7 +387,10 @@ static void handle_app_hide(struct mg_connection *conn, DIALServer *ds = request_info->user_data; int canStop = 0; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); // update the application state @@ -335,17 +404,17 @@ static void handle_app_hide(struct mg_connection *conn, } else { // not implemented in reference DIALStatus status = app->callbacks.hide_cb(ds, app_name, app->run_id, app->callback_data); - if (status!=kDIALStatusHide){ + if (status != kDIALStatusHide){ fprintf(stderr, "Hide not implemented for reference.\n"); mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); - }else{ - app->state = kDIALStatusHide; - mg_printf(conn, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Access-Control-Allow-Origin: %s\r\n" - "\r\n", - origin_header); + } else { + app->state = kDIALStatusHide; + mg_printf(conn, "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Access-Control-Allow-Origin: %s\r\n" + "\r\n", + origin_header); } } ds_unlock(ds); @@ -361,7 +430,10 @@ static void handle_dial_data(struct mg_connection *conn, DIALApp *app; DIALServer *ds = request_info->user_data; - ds_lock(ds); + if (!ds_lock(ds)) { + mg_send_http_error(conn, 500, "500 Internal Server Error", "500 Internal Server Error"); + return; + } app = *find_app(ds, app_name); if (!app) { mg_send_http_error(conn, 404, "Not Found", "Not Found"); @@ -371,21 +443,22 @@ static void handle_dial_data(struct mg_connection *conn, int nread; if (!use_payload) { if (request_info->query_string) { + int qs_len = strlen(request_info->query_string); + if (qs_len > DIAL_DATA_MAX_PAYLOAD) { + mg_send_http_error(conn, 413, "413 Request Entity Too Large", + "413 Request Entity Too Large"); + ds_unlock(ds); + return; + } strncpy(body, request_info->query_string, DIAL_DATA_MAX_PAYLOAD); - nread = strlen(body); + nread = qs_len; } else { nread = 0; } } else { nread = mg_read(conn, body, DIAL_DATA_MAX_PAYLOAD); - body[nread] = '\0'; - } - if (nread > DIAL_DATA_MAX_PAYLOAD) { - mg_send_http_error(conn, 413, "413 Request Entity Too Large", - "413 Request Entity Too Large"); - ds_unlock(ds); - return; } + body[nread] = '\0'; if (isBadPayload(body, nread)) { mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request"); @@ -398,6 +471,7 @@ static void handle_dial_data(struct mg_connection *conn, app->dial_data = parse_params(body); store_dial_data(app->name, app->dial_data); + free_dial_data(&app->dial_data); mg_printf(conn, "HTTP/1.1 200 OK\r\n" "Access-Control-Allow-Origin: %s\r\n" @@ -418,7 +492,7 @@ static int ends_with(const char *str, const char *suffix) { } -// str contains a white space separated list of strings (only supports SPACE characters for now) +// str contains a white space separated list of strings (only supports SPACE characters for now) static int ends_with_in_list (const char *str, const char *list) { if (!str || !list) return 0; @@ -447,12 +521,12 @@ static int ends_with_in_list (const char *str, const char *list) { substring[copyLength] = '\0'; //printf("found %s \n", substring); if (ends_with(str, substring)) { - free(substring); + free(substring); substring = NULL; return 1; } scanPointer = scanPointer + copyLength + 1; // assumption: only 1 character } - free(substring); + free(substring); substring = NULL; return ends_with(str, scanPointer); } @@ -471,7 +545,10 @@ static int is_allowed_origin(DIALServer* ds, char * origin, const char * app_nam return 1; } - ds_lock(ds); + if (!ds_lock(ds)) { + // If we can't check, fail in favor of safety. + return 0; + } DIALApp *app; int result = 0; for (app = ds->apps; app != NULL; app = app->next) { @@ -530,11 +607,16 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, fprintf(stderr, "Origin %s, Host: %s\n", origin_header, host_header); if (event == MG_NEW_REQUEST) { // URL ends with run - if (!strncmp(request_info->uri + strlen(request_info->uri) - 4, RUN_URI, - strlen(RUN_URI))) { - char app_name[256] = {0, }; // assuming the application name is not over 256 chars. - strncpy(app_name, request_info->uri + strlen(APPS_URI), - ((strlen(request_info->uri) - 4) - (sizeof(APPS_URI) - 1))); + if (strlen(request_info->uri) > strlen(RUN_URI) + strlen(APPS_URI) + && !strncmp(request_info->uri + strlen(request_info->uri) - strlen(RUN_URI), RUN_URI, strlen(RUN_URI))) + { + // Maximum app name length of 255 characters. + char app_name[256] = {0, }; + int appname_len = strlen(request_info->uri) - strlen(RUN_URI) - strlen(APPS_URI); + if (appname_len > 255) { + appname_len = 255; + } + strncpy(app_name, request_info->uri + strlen(APPS_URI), appname_len); if (!strcmp(request_info->request_method, "OPTIONS")) { return options_response(ds, conn, host_header, origin_header, app_name, "DELETE, OPTIONS"); @@ -542,7 +624,8 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, // DELETE non-empty app name if (app_name[0] != '\0' - && !strcmp(request_info->request_method, "DELETE")) { + && !strcmp(request_info->request_method, "DELETE")) + { if (host_header && is_allowed_origin(ds, origin_header, app_name)) { handle_app_stop(conn, request_info, app_name, origin_header); } else { @@ -555,10 +638,12 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, } } // URI starts with "/apps/" and is followed by an app name - else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1) - && !strchr(request_info->uri + strlen(APPS_URI), '/')) { + else if (strlen(request_info->uri) > strlen(APPS_URI) + && !strncmp(request_info->uri, APPS_URI, strlen(APPS_URI)) + && !strchr(request_info->uri + strlen(APPS_URI), '/')) + { const char *app_name; - app_name = request_info->uri + sizeof(APPS_URI) - 1; + app_name = request_info->uri + strlen(APPS_URI); if (!strcmp(request_info->request_method, "OPTIONS")) { return options_response(ds, conn, host_header, origin_header, app_name, "GET, POST, OPTIONS"); @@ -576,27 +661,29 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, } else if (!strcmp(request_info->request_method, "GET")) { handle_app_status(conn, request_info, app_name, origin_header); } else { - mg_send_http_error(conn, 501, "Not Implemented", - "Not Implemented"); + mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); } } // URI that ends with HIDE_URI - else if (!strncmp(request_info->uri + strlen(request_info->uri) - strlen(HIDE_URI), HIDE_URI, - strlen(HIDE_URI))) { - char app_name[256] = {0, }; // assuming the application name is not over 256 chars. - strncpy(app_name, request_info->uri + strlen(APPS_URI), - ((strlen(request_info->uri) - strlen(RUN_URI) - strlen(HIDE_URI)) - (sizeof(APPS_URI) - 1))); + else if (strlen(request_info->uri) > strlen(HIDE_URI) + strlen(RUN_URI) + strlen(APPS_URI) + && !strncmp(request_info->uri + strlen(request_info->uri) - strlen(HIDE_URI), HIDE_URI, strlen(HIDE_URI))) + { + // Maximum app name length of 255 characters. + char app_name[256] = {0, }; + int appname_len = strlen(request_info->uri) - strlen(RUN_URI) - strlen(HIDE_URI) - strlen(APPS_URI); + if (appname_len > 255) { + appname_len = 255; + } + strncpy(app_name, request_info->uri + strlen(APPS_URI), appname_len); if (!strcmp(request_info->request_method, "OPTIONS")) { return options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS"); } - if (app_name[0] != '\0' - && !strcmp(request_info->request_method, "POST")) { + if (app_name[0] != '\0' && !strcmp(request_info->request_method, "POST")) { handle_app_hide(conn, request_info, app_name, origin_header); - }else{ - mg_send_http_error(conn, 501, "Not Implemented", - "Not Implemented"); + } else { + mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented"); } } // URI is of the form */app_name/dial_data @@ -607,18 +694,20 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr)); if ( !strncmp(laddr, gLocalhost, strlen(gLocalhost)) ) { char *app_name = parse_app_name(request_info->uri); + if (app_name == NULL) { + mg_send_http_error(conn, 500, "Internal Error", "Internal Error"); + } else { + if (!strcmp(request_info->request_method, "OPTIONS")) { + void *ret = options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS"); + free(app_name); + return ret; + } + int use_payload = strcmp(request_info->request_method, "POST") ? 0 : 1; + handle_dial_data(conn, request_info, app_name, origin_header, + use_payload); - if (!strcmp(request_info->request_method, "OPTIONS")) { - void *ret = options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS"); free(app_name); - return ret; } - int use_payload = - strcmp(request_info->request_method, "POST") ? 0 : 1; - handle_dial_data(conn, request_info, app_name, origin_header, - use_payload); - - free(app_name); } else { // If the request is not from local host, return an error mg_send_http_error(conn, 403, "Forbidden", "Forbidden"); @@ -636,12 +725,19 @@ static void *request_handler(enum mg_event event, struct mg_connection *conn, DIALServer *DIAL_create() { DIALServer *ds = calloc(1, sizeof(DIALServer)); - pthread_mutex_init(&ds->mux, NULL); + if (ds == NULL) { + return NULL; + } + if (pthread_mutex_init(&ds->mux, NULL) != 0) { + free(ds); ds = NULL; + return NULL; + } return ds; } -void DIAL_start(DIALServer *ds) { +int DIAL_start(DIALServer *ds) { ds->ctx = mg_start(&request_handler, ds, DIAL_PORT); + return (ds->ctx != NULL); } void DIAL_stop(DIALServer *ds) { @@ -663,51 +759,60 @@ int DIAL_register_app(DIALServer *ds, const char *app_name, int useAdditionalData, const char* corsAllowedOrigin) { DIALApp **ptr, *app; - int ret; - ds_lock(ds); + if (!ds_lock(ds)) { + return -1; + } ptr = find_app(ds, app_name); if (*ptr != NULL) { // app already registered - ret = 0; + ds_unlock(ds); + return 0; } else { app = malloc(sizeof(DIALApp)); + if (app == NULL) { + ds_unlock(ds); + return -1; + } app->callbacks = *callbacks; app->name = strdup(app_name); + if (app->name == NULL) { + free(app); app = NULL; + ds_unlock(ds); + return -1; + } app->next = *ptr; app->state = kDIALStatusStopped; app->callback_data = user_data; app->dial_data = retrieve_dial_data(app->name); app->useAdditionalData = useAdditionalData; app->corsAllowedOrigin[0] = '\0'; - if (corsAllowedOrigin && - strlen(corsAllowedOrigin) < sizeof(app->corsAllowedOrigin)) { - strcpy(app->corsAllowedOrigin, corsAllowedOrigin); + if (corsAllowedOrigin && strlen(corsAllowedOrigin) < sizeof(app->corsAllowedOrigin)) { + strcpy(app->corsAllowedOrigin, corsAllowedOrigin); } *ptr = app; - ret = 1; + ds_unlock(ds); + return 1; } - ds_unlock(ds); - return ret; } int DIAL_unregister_app(DIALServer *ds, const char *app_name) { DIALApp **ptr, *app; - int ret; - ds_lock(ds); + if (!ds_lock(ds)) { + return -1; + } ptr = find_app(ds, app_name); if (*ptr == NULL) { // no such app - ret = 0; + ds_unlock(ds); + return 0; } else { app = *ptr; *ptr = app->next; - free(app->name); - free(app); - ret = 1; + free(app->name); app->name = NULL; + free(app); app = NULL; + ds_unlock(ds); + return 1; } - - ds_unlock(ds); - return ret; } const char * DIAL_get_payload(DIALServer *ds, const char *app_name) { diff --git a/server/dial_server.h b/server/dial_server.h index 7890dcf..c9b0c7a 100644 --- a/server/dial_server.h +++ b/server/dial_server.h @@ -121,7 +121,7 @@ DIALServer *DIAL_create(); * * @param[in] ds DIAL server handle */ -void DIAL_start(DIALServer *ds); +int DIAL_start(DIALServer *ds); /* * Stop the DIAL server. diff --git a/server/main.c b/server/main.c index fcc64c2..d515c1c 100644 --- a/server/main.c +++ b/server/main.c @@ -112,19 +112,19 @@ int isAppRunning( char *pzName, char *pzCommandPattern ) { struct dirent* procEntry; while((procEntry=readdir(proc_fd)) != NULL) { if( doesMatch( "^[0-9][0-9]*$", procEntry->d_name ) ) { - char exePath[64] = {0,}; + char exePath[384] = {0,}; char link[256] = {0,}; - char cmdlinePath[64] = {0,}; + char cmdlinePath[384] = {0,}; char buffer[1024] = {0,}; int len; - sprintf( exePath, "/proc/%s/exe", procEntry->d_name); - sprintf( cmdlinePath, "/proc/%s/cmdline", procEntry->d_name); + snprintf( exePath, sizeof(exePath), "/proc/%s/exe", procEntry->d_name); + snprintf( cmdlinePath, sizeof(cmdlinePath), "/proc/%s/cmdline", procEntry->d_name); if( (len = readlink( exePath, link, sizeof(link)-1)) != -1 ) { char executable[256] = {0,}; - strcat( executable, pzName ); + strncpy( executable, pzName, sizeof(executable) - 2 ); strcat( executable, "$" ); - // TODO: Make this search for EOL to prevent false positivies + // TODO: Make this search for EOL to prevent false positives if( !doesMatch( executable, link ) ) { continue; } @@ -147,9 +147,9 @@ int isAppRunning( char *pzName, char *pzCommandPattern ) { continue; } } - + int d_name = atoi(procEntry->d_name); closedir(proc_fd); - return atoi(procEntry->d_name); + return d_name; } } @@ -202,14 +202,14 @@ static DIALStatus youtube_start(DIALServer *ds, const char *appname, printf("\n\n ** LAUNCH YouTube ** with payload %s\n\n", payload); char url[512] = {0,}, data[512] = {0,}; - if (strlen(payload) && strlen(additionalDataUrl)){ - sprintf( url, "https://www.youtube.com/tv?%s&%s", payload, additionalDataUrl); - }else if (strlen(payload)){ - sprintf( url, "https://www.youtube.com/tv?%s", payload); - }else{ - sprintf( url, "https://www.youtube.com/tv"); + if (strlen(payload) && strlen(additionalDataUrl)) { + snprintf( url, sizeof(url), "https://www.youtube.com/tv?%s&%s", payload, additionalDataUrl); + } else if (strlen(payload)) { + snprintf( url, sizeof(url), "https://www.youtube.com/tv?%s", payload); + } else { + snprintf( url, sizeof(url), "https://www.youtube.com/tv"); } - sprintf( data, "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") ); + snprintf( data, sizeof(data), "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") ); const char * const youtube_args[] = { spAppYouTubeExecutable, spYouTubePS3UserAgent, @@ -268,13 +268,18 @@ static void setValue( char * pSource, char dest[] ) static void setDataDir(char *pData) { setValue( spNfDataDir, spDataDir ); - strcat(spDataDir, pData); + strncat(spDataDir, pData, sizeof(spDataDir) - 1); } void runDial(void) { DIALServer *ds; ds = DIAL_create(); + if (ds == NULL) { + printf("Unable to create DIAL server.\n"); + return; + } + struct DIALAppCallbacks cb_nf; cb_nf.start_cb = netflix_start; cb_nf.hide_cb = netflix_hide; @@ -283,16 +288,20 @@ void runDial(void) struct DIALAppCallbacks cb_yt = {youtube_start, youtube_hide, youtube_stop, youtube_status}; struct DIALAppCallbacks cb_system = {system_start, system_hide, NULL, system_status}; - DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, ".netflix.com"); - DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, ".youtube.com"); - DIAL_register_app(ds, "system", &cb_system, NULL, 1, ""); - DIAL_start(ds); - - gDialPort = DIAL_get_port(ds); - printf("launcher listening on gDialPort %d\n", gDialPort); - run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid); - - DIAL_stop(ds); + if (DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, ".netflix.com") == -1 || + DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, ".youtube.com") == -1 || + DIAL_register_app(ds, "system", &cb_system, NULL, 1, "") == -1) + { + printf("Unable to register DIAL applications.\n"); + } else if (!DIAL_start(ds)) { + printf("Unable to start DIAL master listening thread.\n"); + } else { + gDialPort = DIAL_get_port(ds); + printf("launcher listening on gDialPort %d\n", gDialPort); + run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid); + + DIAL_stop(ds); + } free(ds); } @@ -317,11 +326,11 @@ static void processOption( int index, char * pOption ) setValue( pOption, spUuid ); break; case 5: - if (strcmp(pOption, "on")==0){ + if (strcmp(pOption, "on")==0) { wakeOnWifiLan=true; - }else if (strcmp(pOption, "off")==0){ + } else if (strcmp(pOption, "off") == 0) { wakeOnWifiLan=false; - }else{ + } else { fprintf(stderr, "Option %s is not valid for %s", pOption, WAKE_OPTION_LONG); exit(1); diff --git a/server/mongoose.c b/server/mongoose.c index 8df8a2b..97f3af3 100644 --- a/server/mongoose.c +++ b/server/mongoose.c @@ -37,6 +37,10 @@ #ifndef BUFSIZ #define BUFSIZ 4096 #endif +#ifndef REASON_SIZ +/* REASON_SIZ must be non-trivially smaller than BUFSIZ. */ +#define REASON_SIZ 2048 +#endif #define MAX_REQUEST_SIZE 4096 #define NUM_THREADS 4 @@ -214,33 +218,12 @@ static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, // Skip the characters until one of the delimiters characters found. // 0-terminate resulting word. Skip the delimiter and following whitespaces if any. // Advance pointer to buffer to the next word. Return found 0-terminated word. -// Delimiters can be quoted with quotechar. -static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) { +static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace) { char *p, *begin_word, *end_word, *end_whitespace; begin_word = *buf; end_word = begin_word + strcspn(begin_word, delimiters); - /* Check for quotechar */ - if (end_word > begin_word) { - p = end_word - 1; - while (*p == quotechar) { - /* If there is anything beyond end_word, copy it */ - if (*end_word == '\0') { - *p = '\0'; - break; - } else { - size_t end_off = strcspn(end_word + 1, delimiters); - memmove (p, end_word, end_off + 1); - p += end_off; /* p must correspond to end_word - 1 */ - end_word += end_off + 1; - } - } - for (p++; p < end_word; p++) { - *p = '\0'; - } - } - if (*end_word == '\0') { *buf = end_word; } else { @@ -259,7 +242,7 @@ static char *skip_quoted(char **buf, const char *delimiters, const char *whitesp // Simplified version of skip_quoted without quote char // and whitespace == delimiters static char *skip(char **buf, const char *delimiters) { - return skip_quoted(buf, delimiters, delimiters, 0); + return skip_quoted(buf, delimiters, delimiters); } @@ -296,7 +279,14 @@ void mg_send_http_error(struct mg_connection *conn, int status, /* Errors 1xx, 204 and 304 MUST NOT send a body */ if (status > 199 && status != 204 && status != 304) { - len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); + // If reason is really long, buf will have been completely filled up. + // Then buf[len] = '\n' will replace the trailing NULL with '\n' and kill buf + // as a string. Plus the next call to mg_vsnprintf() will be writing to an + // invalid location and negative (gigantic because size_t is unsigned) size. + // + // Given how this function is called, the safest thing to do is probably to + // require reason remains relatively short and will get truncated. + len = mg_snprintf(conn, buf, REASON_SIZ, "Error %d: %s", status, reason); cry(conn, "%s", buf); buf[len++] = '\n'; @@ -332,13 +322,11 @@ static int start_thread(struct mg_context *ctx, mg_thread_func_t func, return retval; } -static int set_non_blocking_mode(SOCKET sock) { +static void set_non_blocking_mode(SOCKET sock) { int flags; flags = fcntl(sock, F_GETFL, 0); (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK); - - return 0; } // Write data to the IO channel - opened file descriptor, socket or SSL @@ -350,8 +338,8 @@ static int64_t push(FILE *fp, SOCKET sock, const char *buf, int64_t len) { sent = 0; while (sent < len) { - /* How many bytes we send in this iteration */ - k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent); + /* How many bytes should we send in this iteration */ + k = ((len - sent) > INT_MAX) ? INT_MAX : (int) (len - sent); if (fp != NULL) { n = fwrite(buf + sent, 1, (size_t)k, fp); @@ -458,9 +446,10 @@ static size_t url_decode(const char *src, size_t src_len, char *dst, #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { - if (src[i] == '%' && + if (src[i] == '%' && (i + 2 < src_len) && isxdigit(* (const unsigned char *) (src + i + 1)) && - isxdigit(* (const unsigned char *) (src + i + 2))) { + isxdigit(* (const unsigned char *) (src + i + 2))) + { a = tolower(* (const unsigned char *) (src + i + 1)); b = tolower(* (const unsigned char *) (src + i + 2)); dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); @@ -490,6 +479,7 @@ static int get_request_len(const char *buf, int buflen) { // Control characters are not allowed but >=128 is. if (!isprint(* (const unsigned char *) s) && *s != '\r' && *s != '\n' && * (const unsigned char *) s < 128) { + // FIXME: Why doesn't this immediately return as malformed? len = -1; } else if (s[0] == '\n' && s[1] == '\n') { len = (int) (s - buf) + 2; @@ -529,7 +519,7 @@ static void parse_http_headers(char **buf, struct mg_request_info *ri) { int i; for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) { - ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0); + ri->http_headers[i].name = skip_quoted(buf, ":", " "); ri->http_headers[i].value = skip(buf, "\r\n"); if (ri->http_headers[i].name[0] == '\0') break; @@ -637,6 +627,8 @@ static int set_ports_option(struct mg_context *ctx, int port) { tv.tv_sec = 0; tv.tv_usec = 500 * 1000; + // TODO This code calls close(INVALID_SOCKET), even though close expects positive file descriptors. + // Not sure what the behavior is as a result, might be fine. if ((ctx->local_socket = socket(PF_INET, SOCK_STREAM, 6)) == INVALID_SOCKET || setsockopt(ctx->local_socket, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) != 0 || setsockopt(ctx->local_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0 || @@ -702,22 +694,28 @@ static void close_connection(struct mg_connection *conn) { } static void discard_current_request_from_buffer(struct mg_connection *conn) { - int buffered_len, body_len; + int buffered_len; + int64_t remaining = 0, pull_len = 0, count = 0; + char discard_buffer[16384]; buffered_len = conn->data_len - conn->request_len; assert(buffered_len >= 0); - if (conn->content_len == -1) { - body_len = 0; - } else if (conn->content_len < (int64_t) buffered_len) { - body_len = (int) conn->content_len; - } else { - body_len = buffered_len; + // If there was no specified Content-Length header, then there's no way for + // us to know how much more data needs to be discarded. + // + // Otherwise keep pull()-ing content until we consume the full content length. + if (conn->content_len != -1 && conn->consumed_content < conn->content_len) { + remaining = conn->content_len - conn->consumed_content; + while (remaining > 0) { + pull_len = (remaining < sizeof(discard_buffer)) ? remaining : sizeof(discard_buffer); + count = mg_read(conn, discard_buffer, pull_len); + if (count <= 0) { + break; + } + remaining -= count; + } } - - conn->data_len -= conn->request_len + body_len; - memmove(conn->buf, conn->buf + conn->request_len + body_len, - (size_t) conn->data_len); } static void process_new_connection(struct mg_connection *conn) { @@ -731,7 +729,7 @@ static void process_new_connection(struct mg_connection *conn) { conn->request_len = read_request(conn->client.sock, conn->buf, conn->buf_size, &conn->data_len); } - assert(conn->data_len >= conn->request_len && conn->request_len > 1); + assert(conn->data_len >= conn->request_len); if (conn->request_len == 0 && conn->data_len == conn->buf_size) { mg_send_http_error(conn, 413, "Request Too Large", ""); return; @@ -739,7 +737,7 @@ static void process_new_connection(struct mg_connection *conn) { return; // Remote end closed the connection } - // Nul-terminate the request cause parse_http_request() uses sscanf + // Null-terminate the request because parse_http_request() uses sscanf conn->buf[conn->request_len - 1] = '\0'; if (!parse_http_request(conn->buf, ri)) { // Do not put garbage in the access log, just send it back to the client @@ -752,6 +750,11 @@ static void process_new_connection(struct mg_connection *conn) { // Request is valid, handle it cl = get_header(ri, "Content-Length"); conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10); + if (cl != NULL && conn->content_len < 0) { + mg_send_http_error(conn, 400, "Bad Request", + "Invalid Content-Length header value: [%s]", cl); + return; + } conn->birth_time = time(NULL); handle_request(conn); discard_current_request_from_buffer(conn); @@ -800,9 +803,6 @@ static void worker_thread(struct mg_context *ctx) { // required in the DIAL specification. int buf_size = MAX_REQUEST_SIZE; -#ifndef __APPLE__ - pthread_setname_np( pthread_self(), __func__); -#endif conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size); conn->buf_size = buf_size; conn->buf = (char *) (conn + 1); @@ -820,16 +820,22 @@ static void worker_thread(struct mg_context *ctx) { // Fill in local IP info socklen_t addr_len = sizeof(conn->request_info.local_addr); - getsockname(conn->client.sock, - (struct sockaddr *) &conn->request_info.local_addr, &addr_len); - - process_new_connection(conn); + if (getsockname(conn->client.sock, (struct sockaddr *) &conn->request_info.local_addr, &addr_len) != 0) { + // Something went wrong. + mg_send_http_error(conn, 500, "Internal Server Error", ""); + } else { + process_new_connection(conn); + } close_connection(conn); } free(conn); - // Signal master that we're done with connection and exiting + // Signal master that we're done with connection and exiting. + // + // It is possible that we fail to acquire the mutex and then num_threads will + // end up decrementing incorrectly which may cause the server to hang + // indefinitely while trying to shutdown. But we'll try our best. (void) pthread_mutex_lock(&ctx->mutex); ctx->num_threads--; (void) pthread_cond_signal(&ctx->cond); @@ -839,13 +845,25 @@ static void worker_thread(struct mg_context *ctx) { DEBUG_TRACE(("exiting")); } -// Master thread adds accepted socket to a queue -static void produce_socket(struct mg_context *ctx, const struct socket *sp) { - (void) pthread_mutex_lock(&ctx->mutex); +/** + * Copy an accepted socket onto the queue. Blocks if the queue is full. This + * function is called from the master thread. + * + * @param ctx Mongoose context. + * @param sp the socket. + * @return true if successful, false if there was a mutex error. + */ +static int produce_socket(struct mg_context *ctx, const struct socket *sp) { + if (pthread_mutex_lock(&ctx->mutex) != 0) { + return 0; + }; // If the queue is full, wait while (ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) { - (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex); + if (pthread_cond_wait(&ctx->sq_empty, &ctx->mutex) != 0) { + (void) pthread_mutex_unlock(&ctx->mutex); + return 0; + }; } assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue)); @@ -854,17 +872,16 @@ static void produce_socket(struct mg_context *ctx, const struct socket *sp) { ctx->sq_head++; DEBUG_TRACE(("queued socket %d", sp->sock)); + // Nothing to do if there is an error on signal. But if we fail to unlock + // then we're in a bad state. (void) pthread_cond_signal(&ctx->sq_full); - (void) pthread_mutex_unlock(&ctx->mutex); + return (pthread_mutex_unlock(&ctx->mutex) == 0); } static void master_thread(struct mg_context *ctx) { struct socket accepted; -#ifndef __APPLE__ - pthread_setname_np( pthread_self(), __func__); -#endif socklen_t sock_len = sizeof(accepted.local_addr); memcpy(&accepted.local_addr, &ctx->local_address, sock_len); @@ -877,7 +894,10 @@ static void master_thread(struct mg_context *ctx) { if (accepted.sock != INVALID_SOCKET) { // Put accepted socket structure into the queue. DEBUG_TRACE(("accepted socket %d", accepted.sock)); - produce_socket(ctx, &accepted); + // If the socket fails, trigger stop and try to exit gracefully. + if (!produce_socket(ctx, &accepted)) { + ctx->stop_flag = 1; + }; } } DEBUG_TRACE(("stopping workers")); @@ -886,14 +906,18 @@ static void master_thread(struct mg_context *ctx) { close_all_listening_sockets(ctx); // Wakeup workers that are waiting for connections to handle. - pthread_cond_broadcast(&ctx->sq_full); + // Nothing we can do if there is an error. + (void) pthread_cond_broadcast(&ctx->sq_full); - // Wait until all threads finish - (void) pthread_mutex_lock(&ctx->mutex); - while (ctx->num_threads > 0) { - (void) pthread_cond_wait(&ctx->cond, &ctx->mutex); + // Wait until all threads finish. + // If we cannot acquire the lock, we're in a bad state so skip this and + // just try to clean up and shut down. + if (pthread_mutex_lock(&ctx->mutex) == 0) { + while (ctx->num_threads > 0) { + (void) pthread_cond_wait(&ctx->cond, &ctx->mutex); + } + (void) pthread_mutex_unlock(&ctx->mutex); } - (void) pthread_mutex_unlock(&ctx->mutex); // All threads exited, no sync is needed. Destroy mutex and condvars (void) pthread_mutex_destroy(&ctx->mutex); @@ -925,10 +949,13 @@ void mg_stop(struct mg_context *ctx) { struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int port) { struct mg_context *ctx; - + int retval; + // Allocate context and initialize reasonable general case defaults. - // TODO(lsm): do proper error handling here. ctx = (struct mg_context *) calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return NULL; + } ctx->user_callback = user_callback; ctx->user_data = user_data; @@ -937,15 +964,26 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int po return NULL; } // Ignore SIGPIPE signal, so if browser cancels the request, it - // won't kill the whole process. - (void) signal(SIGPIPE, SIG_IGN); - (void) pthread_mutex_init(&ctx->mutex, NULL); - (void) pthread_cond_init(&ctx->cond, NULL); - (void) pthread_cond_init(&ctx->sq_empty, NULL); - (void) pthread_cond_init(&ctx->sq_full, NULL); + // won't kill the whole + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + free_context(ctx); + return NULL; + }; + if (pthread_mutex_init(&ctx->mutex, NULL) != 0 || + pthread_cond_init(&ctx->cond, NULL) != 0 || + pthread_cond_init(&ctx->sq_empty, NULL) != 0 || + pthread_cond_init(&ctx->sq_full, NULL) != 0) + { + free_context(ctx); + return NULL; + }; // Start master (listening) thread - start_thread(ctx, (mg_thread_func_t) master_thread, ctx); + retval = start_thread(ctx, (mg_thread_func_t) master_thread, ctx); + if (retval != 0) { + free_context(ctx); + return NULL; + } // Start worker threads for (int i = 0; i < NUM_THREADS; i++) { diff --git a/server/nf_callbacks.c b/server/nf_callbacks.c index ce85b5a..ae414c9 100644 --- a/server/nf_callbacks.c +++ b/server/nf_callbacks.c @@ -14,8 +14,14 @@ extern char *spAppNetflix; extern char spNetflix[]; static char *defaultLaunchParam = "source_type=12"; -// Adding 20 bytes for prepended source_type for Netflix -static char sQueryParam[DIAL_MAX_PAYLOAD+DIAL_MAX_ADDITIONALURL+40]; +// Adding 40 bytes for defaultLaunchParam, plus additional characters that +// may get added. +// +// dial_server.c ensures the payload is <= DIAL_MAX_PAYLOAD but since we +// URL-encode it, the total string length may triple. +// +// dial_server.c ensures the additional data URL is <= DIAL_MAX_ADDITIONALURL. +static char sQueryParam[3 * DIAL_MAX_PAYLOAD + DIAL_MAX_ADDITIONALURL + 40]; int isAppRunning( char *pzName, char *pzCommandPattern ); int shouldRelaunch(DIALServer *pServer, const char *pAppName, const char *args ); @@ -34,21 +40,31 @@ DIALStatus netflix_start(DIALServer *ds, const char *appname, // construct the payload to determine if it has changed from the previous launch memset( sQueryParam, 0, sizeof(sQueryParam) ); - strcat( sQueryParam, defaultLaunchParam ); - if(strlen(payload)) + strncpy( sQueryParam, defaultLaunchParam, sizeof(sQueryParam) - 1); + if(strlen(payload)) { char * pUrlEncodedParams; pUrlEncodedParams = url_encode( payload ); if( pUrlEncodedParams ){ - strcat( sQueryParam, "&dial="); - strcat( sQueryParam, pUrlEncodedParams ); - free( pUrlEncodedParams ); + if (strlen(sQueryParam) + sizeof("&dial=") + strlen(pUrlEncodedParams) < sizeof(sQueryParam)) { + strcat( sQueryParam, "&dial=" ); + strcat( sQueryParam, pUrlEncodedParams ); + free( pUrlEncodedParams ); + } else { + free( pUrlEncodedParams ); + return kDIALStatusError; + } + } } if(strlen(additionalDataUrl)){ - strcat(sQueryParam, "&"); - strcat(sQueryParam, additionalDataUrl); + if (strlen(sQueryParam) + sizeof("&") + strlen(additionalDataUrl) < sizeof(sQueryParam)) { + strcat(sQueryParam, "&"); + strcat(sQueryParam, additionalDataUrl); + } else { + return kDIALStatusError; + } } printf("appPid = %s, shouldRelaunch = %s queryParams = %s\n", diff --git a/server/quick_ssdp.c b/server/quick_ssdp.c index a957a2a..e869f4b 100644 --- a/server/quick_ssdp.c +++ b/server/quick_ssdp.c @@ -35,13 +35,6 @@ #include "mongoose.h" #include -#ifdef __APPLE__ -#include -#include -#include -#endif - - // TODO: Partners should define this port #define SSDP_PORT (56790) static char gBuf[4096]; @@ -85,8 +78,7 @@ static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n" static const char wakeup_header[] = "WAKEUP: MAC=%s;Timeout=%d\r\n"; #define STR_TIMEOUTLEN 6 /* Longest is 32767 */ #define HW_ADDRSTRLEN 18 -static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1"; -static char hw_addr[HW_ADDRSTRLEN] = "00:00:00:00:00:00"; +static char ip_addr[INET6_ADDRSTRLEN] = "127.0.0.1"; static int dial_port = 0; static int my_port = 0; static char friendly_name[256]; @@ -115,35 +107,24 @@ static void *request_handler(enum mg_event event, return NULL; } -#ifdef __APPLE__ -static void get_local_address() { - struct ifaddrs* iflist; - char *if_name= "en0"; - char buf[INET6_ADDRSTRLEN]; - - if (getifaddrs(&iflist) == 0) { - for (struct ifaddrs* cur = iflist; cur; cur = cur->ifa_next) { - if (strcmp(cur->ifa_name, if_name) == 0) { - if ((cur->ifa_addr->sa_family == AF_LINK) && cur->ifa_addr) { - unsigned char mac[6]; - struct sockaddr_dl* sdl = (struct sockaddr_dl*)cur->ifa_addr; - memcpy(mac, LLADDR(sdl), sdl->sdl_alen); - sprintf(hw_addr, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - } - - if (cur->ifa_addr->sa_family == AF_INET) { - void *tmp = &((struct sockaddr_in *)cur->ifa_addr)->sin_addr; - strcpy(ip_addr, inet_ntop(cur->ifa_addr->sa_family, tmp, buf, sizeof(buf))); - } - } - } - freeifaddrs(iflist); - } -} -#else -static void get_local_address() { +/** + * Returns the local hardware address (e.g. MAC address). On macOS the "en0" + * interface is used. On other platforms the first non-loopback interface is + * used. + * + * As a side-effect, the local global ip_addr is also populated. + * + * (Are these choices of interface really the right ones? Seems risky for + * multi-homed systems.) + * + * @return the local hardware address or NULL if it does not exist, cannot + * be retrieved, or out-of-memory. The caller must free the returned + * memory. + */ +static char * get_local_address() { struct ifconf ifc; char buf[4096]; + char * hw_addr = NULL; int s, i; if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) { perror("socket"); @@ -160,8 +141,8 @@ static void get_local_address() { exit(1); } for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) { - strcpy(ip_addr, - inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr)); + strncpy(ip_addr, + inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr), sizeof(ip_addr) - 1); if (0 > ioctl(s, SIOCGIFFLAGS, &ifc.ifc_req[i])) { perror("SIOCGIFFLAGS"); exit(1); @@ -174,6 +155,14 @@ static void get_local_address() { perror("SIOCGIFHWADDR"); exit(1); } + // FIXME: How do I figure out what type of interface this is in order to + // cast the struct sockaddr ifr_hraddr to the proper type and extract the + // hardware address? + // + // Make sure this is correct for the target device and platform. + hw_addr = (char*)malloc(HW_ADDRSTRLEN + 1); + if (hw_addr == NULL) + break; sprintf(hw_addr, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifc.ifc_req[i].ifr_hwaddr.sa_data[0], (unsigned char)ifc.ifc_req[i].ifr_hwaddr.sa_data[1], @@ -184,14 +173,14 @@ static void get_local_address() { break; } close(s); + return hw_addr; } -#endif -static void handle_mcast() { +static void handle_mcast(char *hw_addr) { int s, one = 1, bytes; socklen_t addrlen; - struct sockaddr_in saddr; - struct ip_mreq mreq; + struct sockaddr_in saddr = {0}; + struct ip_mreq mreq = {0}; char wakeup_buf[sizeof(wakeup_header) + HW_ADDRSTRLEN + STR_TIMEOUTLEN] = {0, }; char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256 + sizeof(wakeup_buf)] = {0,}; int send_size; @@ -208,11 +197,7 @@ static void handle_mcast() { exit(1); } saddr.sin_family = AF_INET; -#ifdef __APPLE__ - saddr.sin_addr.s_addr = INADDR_ANY; -#else saddr.sin_addr.s_addr = inet_addr("239.255.255.250"); -#endif saddr.sin_port = htons(1900); if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) { @@ -261,32 +246,42 @@ static void handle_mcast() { } void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid) { + char *hw_addr = NULL; struct sockaddr sa; socklen_t len = sizeof(sa); if(pFriendlyName) { strncpy(friendly_name, pFriendlyName, sizeof(friendly_name)); - friendly_name[255] = '\0'; + friendly_name[sizeof(friendly_name) - 1] = '\0'; } else { strcpy(friendly_name, "DIAL server sample"); } if(pModelName) { strncpy(model_name, pModelName, sizeof(model_name)); - uuid[255] = '\0'; + model_name[sizeof(model_name) - 1] = '\0'; } else { strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef"); } if(pUuid) { strncpy(uuid, pUuid, sizeof(uuid)); - uuid[255] = '\0'; + uuid[sizeof(uuid) - 1] = '\0'; } else { strcpy(uuid, "deadbeef-dead-beef-dead-beefdeadbeef"); } dial_port = port; - get_local_address(); - ctx = mg_start(&request_handler, NULL, SSDP_PORT); - if (mg_get_listen_addr(ctx, &sa, &len)) { - my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port); + hw_addr = get_local_address(); + if (hw_addr == NULL) { + printf("Unable to retrieve hardware address."); + return; } - printf("SSDP listening on %s:%d\n", ip_addr, my_port); - handle_mcast(); + ctx = mg_start(&request_handler, NULL, SSDP_PORT); + if (ctx == NULL) { + printf("Unable to start SSDP master listening thread."); + } else { + if (mg_get_listen_addr(ctx, &sa, &len)) { + my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port); + } + printf("SSDP listening on %s:%d\n", ip_addr, my_port); + handle_mcast(hw_addr); + } + free(hw_addr); hw_addr = NULL; } diff --git a/server/system_callbacks.c b/server/system_callbacks.c index cf03f72..9a2d14f 100644 --- a/server/system_callbacks.c +++ b/server/system_callbacks.c @@ -20,8 +20,8 @@ DIALStatus system_start(DIALServer *ds, const char *appname, const char *payload if (strlen(spSleepPassword) != 0) { /* Look for key */ - char *key_value; - if ( (key_value = strchr(query_string, '&')) == '\0' ) { + char *key_value = strchr(query_string, '&'); + if ( key_value == NULL || *key_value == '\0' ) { return kDIALStatusErrorForbidden; // No key specified. } diff --git a/server/url_lib.c b/server/url_lib.c index 1807814..99d1d13 100644 --- a/server/url_lib.c +++ b/server/url_lib.c @@ -30,12 +30,16 @@ #include #include -char* smartstrcat(char* dest, char* src, size_t max_chars) { - size_t copied = 0; - while ((*(dest++) = *(src++)) && copied++ < max_chars) { +static const char * unknown_str = "unknown"; + +char* smartstrncpy(char* dest, char* src, size_t max_chars) { + size_t copied; + for (copied = 0; copied < max_chars; copied++, dest++, src++) { + *dest = *src; + if (*dest == 0) + break; } - // In case we over-stepped the size, end the string nicely. - *(--dest) = '\0'; + *dest = 0; return dest; } @@ -89,23 +93,33 @@ void xmlencode(char *dst, const char *src, size_t max_size) { while (*src && current_size < max_size) { switch (*src) { case '&': - dst = smartstrcat(dst, "&", max_size - current_size); + if (current_size + 5 >= max_size) + break; + dst = smartstrncpy(dst, "&", max_size - current_size); current_size += 5; break; case '\"': - dst = smartstrcat(dst, """, max_size - current_size); + if (current_size + 6 >= max_size) + break; + dst = smartstrncpy(dst, """, max_size - current_size); current_size += 6; break; case '\'': - dst = smartstrcat(dst, "'", max_size - current_size); + if (current_size + 6 >= max_size) + break; + dst = smartstrncpy(dst, "'", max_size - current_size); current_size += 6; break; case '<': - dst = smartstrcat(dst, "<", max_size - current_size); + if (current_size + 4 >= max_size) + break; + dst = smartstrncpy(dst, "<", max_size - current_size); current_size += 4; break; case '>': - dst = smartstrcat(dst, ">", max_size - current_size); + if (current_size + 4 >= max_size) + break; + dst = smartstrncpy(dst, ">", max_size - current_size); current_size += 4; break; default: @@ -119,18 +133,34 @@ void xmlencode(char *dst, const char *src, size_t max_size) { } char *parse_app_name(const char *uri) { + char *unknown = NULL; if (uri == NULL) { - return "unknown"; + unknown = (char*)calloc(strlen(unknown_str) + 1, sizeof(char)); + if (unknown == NULL) { + return NULL; + } + strncpy(unknown, unknown_str, strlen(unknown_str) + 1); + return unknown; } char *slash = strrchr(uri, '/'); if (slash == NULL || slash == uri) { - return "unknown"; + unknown = (char*)calloc(strlen(unknown_str) + 1, sizeof(char)); + if (unknown == NULL) { + return NULL; + } + strncpy(unknown, unknown_str, strlen(unknown_str) + 1); + return unknown; } char *begin = slash; while ((begin != uri) && (*--begin != '/')) ; - begin++; // skip the slash - char *result = (char *) calloc(1, slash - begin+1); + if (*begin == '/') { + begin++; // skip the slash + } + char *result = (char *) calloc(slash - begin+1, sizeof(char)); + if (result == NULL) { + return NULL; + } strncpy(result, begin, slash - begin); result[slash-begin]='\0'; return result; @@ -151,6 +181,9 @@ char *parse_param(char *query_string, char *param_name) { end++; int result_size = end - start; char *result = malloc(result_size + 1); + if (result == NULL) { + return NULL; + } result[0] = '\0'; strncpy(result, start, result_size); result[result_size] = '\0'; @@ -165,20 +198,46 @@ DIALData *parse_params(char * query_string) { query_string++; // skip leading question mark } DIALData *result = NULL; + int err = 0; char *query_string_dup = strdup(query_string); char * name_value = strtok(query_string_dup, "&"); while (name_value != NULL) { DIALData *tmp = (DIALData *) malloc(sizeof(DIALData)); + if (tmp == NULL) { + err = 1; + break; + } size_t name_value_length = strlen(name_value); - tmp->key = (char *) malloc(name_value_length); - tmp->value = (char *) malloc(name_value_length); - sscanf(name_value, "%[^=]=%s", tmp->key, tmp->value); + tmp->key = (char *) calloc(name_value_length + 1, sizeof(char)); + if (tmp->key == NULL) { + free(tmp); tmp = NULL; + err = 1; + break; + } + tmp->value = (char *) calloc(name_value_length + 1, sizeof(char)); + if (tmp->value == NULL) { + free(tmp->key); tmp->key = NULL; + free(tmp); tmp = NULL; + err = 1; + break; + } + int match = sscanf(name_value, "%[^=]=%s", tmp->key, tmp->value); + if (match != 2) { + free(tmp->value); tmp->value = NULL; + free(tmp->key); tmp->key = NULL; + free(tmp); tmp = NULL; + err = 1; + break; + } tmp->next = result; result = tmp; name_value = strtok(NULL, "&"); // read next token } free(query_string_dup); + if (err) { + free_dial_data(&result); result = NULL; + } return result; } @@ -194,7 +253,7 @@ char from_hex(char ch) { /* Converts an integer value to its hex character*/ char to_hex(char code) { static char hex[] = "0123456789abcdef"; - return hex[code & 15]; + return hex[code & 15]; } diff --git a/server/url_lib.h b/server/url_lib.h index 1a9f750..537a790 100644 --- a/server/url_lib.h +++ b/server/url_lib.h @@ -34,7 +34,7 @@ * Concatenate a maxim of max_chars characters from src into dest, * and return a pointer to the last character in dest. */ -char* smartstrcat(char* dest, char* src, size_t max_chars); +char* smartstrncpy(char* dest, char* src, size_t max_chars); int urldecode(char *dst, const char *src, size_t max_size);