/* * This file is part of MotionPlus. * * MotionPlus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MotionPlus is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MotionPlus. If not, see . * */ #include "motionplus.hpp" #include "camera.hpp" #include "conf.hpp" #include "logger.hpp" #include "util.hpp" #include "webu.hpp" #include "webu_ans.hpp" #include "webu_html.hpp" #include "webu_common.hpp" #include "webu_stream.hpp" #include "webu_mpegts.hpp" #include "webu_json.hpp" #include "webu_post.hpp" #include "webu_file.hpp" #include "video_v4l2.hpp" /* Extract the camid and cmds from the url */ int cls_webu_ans::parseurl() { char *tmpurl; size_t pos_slash1, pos_slash2, baselen; /* Example: /camid/cmd1/cmd2/cmd3 */ uri_camid = ""; uri_cmd1 = ""; uri_cmd2 = ""; uri_cmd3 = ""; MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO, _("Sent url: %s"),url.c_str()); tmpurl = (char*)mymalloc(url.length()+1); memcpy(tmpurl, url.c_str(), url.length()); MHD_http_unescape(tmpurl); url.assign(tmpurl); free(tmpurl); MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO, _("Decoded url: %s"),url.c_str()); baselen = app->cfg->webcontrol_base_path.length(); if (url.length() < baselen) { return -1; } if (url.substr(baselen) == "/favicon.ico") { return -1; } if (url.substr(0, baselen) != app->cfg->webcontrol_base_path) { return -1; } if (url == "/") { return 0; } /* Remove any trailing slash to keep parms clean */ if (url.substr(url.length()-1,1) == "/") { url = url.substr(0, url.length()-1); } if (url.length() == baselen) { return 0; } pos_slash1 = url.find("/", baselen+1); if (pos_slash1 != std::string::npos) { uri_camid = url.substr(baselen+1, pos_slash1-baselen- 1); } else { uri_camid = url.substr(baselen+1); return 0; } pos_slash1++; if (pos_slash1 >= url.length()) { return 0; } pos_slash2 = url.find("/", pos_slash1); if (pos_slash2 != std::string::npos) { uri_cmd1 = url.substr(pos_slash1, pos_slash2 - pos_slash1); } else { uri_cmd1 = url.substr(pos_slash1); return 0; } pos_slash1 = ++pos_slash2; if (pos_slash1 >= url.length()) { return 0; } if (uri_cmd1 == "movies") { /* Whole remaining url is the movie name and possibly subdir */ uri_cmd2 = url.substr(pos_slash1); return 0; } else { pos_slash2 = url.find("/", pos_slash1); if (pos_slash2 != std::string::npos) { uri_cmd2 = url.substr(pos_slash1, pos_slash2 - pos_slash1); } else { uri_cmd2 = url.substr(pos_slash1); return 0; } pos_slash1 = ++pos_slash2; if (pos_slash1 >= url.length()) { return 0; } uri_cmd3 = url.substr(pos_slash1); } return 0; } /* Edit the parameters specified in the url sent */ void cls_webu_ans::parms_edit() { int indx, is_nbr; if (parseurl() != 0) { uri_camid = ""; uri_cmd1 = ""; uri_cmd2 = ""; uri_cmd3 = ""; url = ""; } if (uri_camid.length() > 0) { is_nbr = true; for (indx=0; indx < (int)uri_camid.length(); indx++) { if ((uri_camid[(uint)indx] > '9') || (uri_camid[(uint)indx] < '0')) { is_nbr = false; } } if (is_nbr) { device_id = atoi(uri_camid.c_str()); } } for (indx=0; indxcam_cnt; indx++) { if (app->cam_list[indx]->device_id == device_id) { camindx = indx; cam = app->cam_list[indx]; } } MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO , "camid: >%s< camindx: >%d< cmd1: >%s< cmd2: >%s< cmd3: >%s<" , uri_camid.c_str(), camindx , uri_cmd1.c_str(), uri_cmd2.c_str() , uri_cmd3.c_str()); } /* Log the ip of the client connecting*/ void cls_webu_ans::clientip_get() { const union MHD_ConnectionInfo *con_info; char client[WEBUI_LEN_URLI]; const char *ip_dst; struct sockaddr_in6 *con_socket6; struct sockaddr_in *con_socket4; int is_ipv6; is_ipv6 = false; if (app->cfg->webcontrol_ipv6) { is_ipv6 = true; } con_info = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS); if (is_ipv6) { con_socket6 = (struct sockaddr_in6 *)con_info->client_addr; ip_dst = inet_ntop(AF_INET6, &con_socket6->sin6_addr, client, WEBUI_LEN_URLI); if (ip_dst == NULL) { clientip = "Unknown"; } else { clientip.assign(client); if (clientip.substr(0, 7) == "::ffff:") { clientip = clientip.substr(7); } } } else { con_socket4 = (struct sockaddr_in *)con_info->client_addr; ip_dst = inet_ntop(AF_INET, &con_socket4->sin_addr, client, WEBUI_LEN_URLI); if (ip_dst == NULL) { clientip = "Unknown"; } else { clientip.assign(client); } } } /* Get the hostname */ void cls_webu_ans::hostname_get() { const char *hdr; hdr = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_HOST); if (hdr == NULL) { hostfull = "//localhost:" + std::to_string(app->cfg->webcontrol_port) + app->cfg->webcontrol_base_path; } else { hostfull = "//" + std::string(hdr) + app->cfg->webcontrol_base_path; } MOTPLS_LOG(DBG,TYPE_ALL, NO_ERRNO, _("Full Host: %s"), hostfull.c_str()); return; } /* Log the failed authentication check */ void cls_webu_ans::failauth_log(bool userid_fail) { timespec tm_cnct; ctx_webu_clients clients; std::list::iterator it; MOTPLS_LOG(ALR, TYPE_STREAM, NO_ERRNO ,_("Failed authentication from %s"), clientip.c_str()); clock_gettime(CLOCK_MONOTONIC, &tm_cnct); it = webu->wb_clients.begin(); while (it != webu->wb_clients.end()) { if (it->clientip == clientip) { it->conn_nbr++; it->conn_time.tv_sec =tm_cnct.tv_sec; it->authenticated = false; if (userid_fail) { it->userid_fail_nbr++; } return; } it++; } clients.clientip = clientip; clients.conn_nbr = 1; clients.conn_time = tm_cnct; clients.authenticated = false; if (userid_fail) { clients.userid_fail_nbr = 1; } else { clients.userid_fail_nbr = 0; } webu->wb_clients.push_back(clients); return; } void cls_webu_ans::client_connect() { timespec tm_cnct; ctx_webu_clients clients; std::list::iterator it; clock_gettime(CLOCK_MONOTONIC, &tm_cnct); /* First we need to clean out any old IPs from the list*/ it = webu->wb_clients.begin(); while (it != webu->wb_clients.end()) { if ((tm_cnct.tv_sec - it->conn_time.tv_sec) >= (app->cfg->webcontrol_lock_minutes*60)) { it = webu->wb_clients.erase(it); } it++; } /* When this function is called, we know that we are authenticated * so we reset the info and as needed print a message that the * ip is connected. */ it = webu->wb_clients.begin(); while (it != webu->wb_clients.end()) { if (it->clientip == clientip) { if (it->authenticated == false) { MOTPLS_LOG(INF,TYPE_ALL, NO_ERRNO, _("Connection from: %s"),clientip.c_str()); } it->authenticated = true; it->conn_nbr = 1; it->userid_fail_nbr = 0; it->conn_time.tv_sec = tm_cnct.tv_sec; return; } it++; } /* The ip was not already in our list. */ clients.clientip = clientip; clients.conn_nbr = 1; clients.userid_fail_nbr = 0; clients.conn_time = tm_cnct; clients.authenticated = true; webu->wb_clients.push_back(clients); MOTPLS_LOG(INF,TYPE_ALL, NO_ERRNO, _("Connection from: %s"),clientip.c_str()); return; } /* Check for ips with excessive failed authentication attempts */ mhdrslt cls_webu_ans::failauth_check() { timespec tm_cnct; std::list::iterator it; std::string tmp; if (webu->wb_clients.size() == 0) { return MHD_YES; } clock_gettime(CLOCK_MONOTONIC, &tm_cnct); it = webu->wb_clients.begin(); while (it != webu->wb_clients.end()) { if ((it->clientip == clientip) && ((tm_cnct.tv_sec - it->conn_time.tv_sec) < (app->cfg->webcontrol_lock_minutes*60)) && (it->authenticated == false) && (it->conn_nbr > app->cfg->webcontrol_lock_attempts)) { MOTPLS_LOG(EMG, TYPE_STREAM, NO_ERRNO , "Ignoring connection from: %s" , clientip.c_str()); it->conn_time = tm_cnct; if (app->cfg->webcontrol_lock_script != "") { tmp = app->cfg->webcontrol_lock_script + " " + std::to_string(it->userid_fail_nbr) + " " + clientip; util_exec_command(cam, tmp.c_str(), NULL); } return MHD_NO; } else if ((tm_cnct.tv_sec - it->conn_time.tv_sec) >= (app->cfg->webcontrol_lock_minutes*60)) { it = webu->wb_clients.erase(it); } else { it++; } } return MHD_YES; } /* Create a authorization denied response to user*/ mhdrslt cls_webu_ans::mhd_digest_fail(int signal_stale) { struct MHD_Response *response; mhdrslt retcd; authenticated = false; resp_page = "Access denied" "Access denied"; response = MHD_create_response_from_buffer(resp_page.length() ,(void *)resp_page.c_str(), MHD_RESPMEM_PERSISTENT); if (response == NULL) { return MHD_NO; } retcd = MHD_queue_auth_fail_response(connection, auth_realm ,auth_opaque, response ,(signal_stale == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO); MHD_destroy_response(response); return retcd; } /* Perform digest authentication */ mhdrslt cls_webu_ans::mhd_digest() { /* This function gets called a couple of * times by MHD during the authentication process. */ int retcd; char *user; /*Get username or prompt for a user/pass */ user = MHD_digest_auth_get_username(connection); if (user == NULL) { return mhd_digest_fail(MHD_NO); } /* Check for valid user name */ if (mystrne(user, auth_user)) { failauth_log(true); myfree(user); return mhd_digest_fail(MHD_NO); } myfree(user); /* Check the password as well*/ retcd = MHD_digest_auth_check(connection, auth_realm , auth_user, auth_pass, 300); if (retcd == MHD_NO) { failauth_log(false); } if ( (retcd == MHD_INVALID_NONCE) || (retcd == MHD_NO) ) { return mhd_digest_fail(retcd); } authenticated = true; return MHD_YES; } /* Create a authorization denied response to user*/ mhdrslt cls_webu_ans::mhd_basic_fail() { struct MHD_Response *response; int retcd; authenticated = false; resp_page = "Access denied" "Access denied"; response = MHD_create_response_from_buffer(resp_page.length() ,(void *)resp_page.c_str(), MHD_RESPMEM_PERSISTENT); if (response == NULL) { return MHD_NO; } retcd = MHD_queue_basic_auth_fail_response (connection, auth_realm, response); MHD_destroy_response(response); if (retcd == MHD_YES) { return MHD_YES; } else { return MHD_NO; } } /* Perform Basic Authentication. */ mhdrslt cls_webu_ans::mhd_basic() { char *user, *pass; pass = NULL; user = NULL; user = MHD_basic_auth_get_username_password (connection, &pass); if ((user == NULL) || (pass == NULL)) { myfree(user); myfree(pass); return mhd_basic_fail(); } if ((mystrne(user, auth_user)) || (mystrne(pass, auth_pass))) { failauth_log(mystrne(user, auth_user)); myfree(user); myfree(pass); return mhd_basic_fail(); } myfree(user); myfree(pass); authenticated = true; return MHD_YES; } /* Parse apart the user:pass provided*/ void cls_webu_ans::mhd_auth_parse() { int auth_len; char *col_pos; myfree(auth_user); myfree(auth_pass); auth_len = (int)app->cfg->webcontrol_authentication.length(); col_pos =(char*) strstr(app->cfg->webcontrol_authentication.c_str() ,":"); if (col_pos == NULL) { auth_user = (char*)mymalloc((uint)(auth_len+1)); auth_pass = (char*)mymalloc(2); snprintf(auth_user, (uint)auth_len + 1, "%s" ,app->cfg->webcontrol_authentication.c_str()); snprintf(auth_pass, 2, "%s",""); } else { auth_user = (char*)mymalloc((uint)auth_len - strlen(col_pos) + 1); auth_pass =(char*)mymalloc(strlen(col_pos)); snprintf(auth_user, (uint)auth_len - strlen(col_pos) + 1, "%s" ,app->cfg->webcontrol_authentication.c_str()); snprintf(auth_pass, strlen(col_pos), "%s", col_pos + 1); } } /* Initialize for authorization */ mhdrslt cls_webu_ans::mhd_auth() { unsigned int rand1,rand2; srand((unsigned int)time(NULL)); rand1 = (unsigned int)(42000000.0 * rand() / (RAND_MAX + 1.0)); rand2 = (unsigned int)(42000000.0 * rand() / (RAND_MAX + 1.0)); snprintf(auth_opaque, WEBUI_LEN_PARM, "%08x%08x", rand1, rand2); snprintf(auth_realm, WEBUI_LEN_PARM, "%s","Motion"); if (app->cfg->webcontrol_authentication == "") { authenticated = true; if (app->cfg->webcontrol_auth_method != "none") { MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("No webcontrol user:pass provided")); } return MHD_YES; } if (auth_user == NULL) { mhd_auth_parse(); } if (app->cfg->webcontrol_auth_method == "basic") { return mhd_basic(); } else if (app->cfg->webcontrol_auth_method == "digest") { return mhd_digest(); } authenticated = true; return MHD_YES; } /* Send the response that we created back to the user. */ void cls_webu_ans::mhd_send() { mhdrslt retcd; struct MHD_Response *response; p_lst *lst = &webu->wb_headers->params_array; p_it it; response = MHD_create_response_from_buffer(resp_page.length() ,(void *)resp_page.c_str(), MHD_RESPMEM_PERSISTENT); if (!response) { MOTPLS_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response")); return; } if (webu->wb_headers->params_count > 0) { for (it = lst->begin(); it != lst->end(); it++) { MHD_add_response_header (response , it->param_name.c_str(),it->param_value.c_str()); } } if (resp_type == WEBUI_RESP_TEXT) { MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain;"); } else if (resp_type == WEBUI_RESP_JSON) { MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json;"); } else { MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); } retcd = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); if (retcd == MHD_NO) { MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("send page failed.")); } } void cls_webu_ans::bad_request() { resp_page = "\n" "\n" "\n" "

Bad Request

\n" "

The server did not understand your request.

\n" "\n" "\n"; mhd_send(); } /* Answer the get request from the user */ void cls_webu_ans::answer_get() { MOTPLS_LOG(DBG, TYPE_STREAM, NO_ERRNO ,"processing get"); if ((uri_cmd1 == "mjpg") || (uri_cmd1 == "mpegts") || (uri_cmd1 == "static")) { if (webu_stream == nullptr) { webu_stream = new cls_webu_stream(this); } webu_stream->main(); } else if (uri_cmd1 == "movies") { if (webu_file == nullptr) { webu_file = new cls_webu_file(this); } webu_file->main(); } else if ((uri_cmd1 == "config.json") || (uri_cmd1 == "movies.json") || (uri_cmd1 == "status.json")) { if (webu_json == nullptr) { webu_json = new cls_webu_json(this); } webu_json->main(); } else { if (webu_html == nullptr) { webu_html = new cls_webu_html(this); } webu_html->main(); } } /* Answer the connection request for the webcontrol*/ mhdrslt cls_webu_ans::answer_main(struct MHD_Connection *p_connection , const char *method, const char *upload_data, size_t *upload_data_size) { mhdrslt retcd; cnct_type = WEBUI_CNCT_CONTROL; connection = p_connection; if (url.length() == 0) { bad_request(); return MHD_YES; } if (cam != NULL) { if (cam->handler_stop) { MOTPLS_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Shutting down camera")); return MHD_NO; } } if (clientip.length() == 0) { clientip_get(); } if (failauth_check() == MHD_NO) { return MHD_NO; } if (authenticated == false) { retcd = mhd_auth(); if (authenticated == false) { return retcd; } } client_connect(); if (mhd_first) { mhd_first = false; if (mystreq(method,"POST")) { if (webu_post == nullptr) { webu_post = new cls_webu_post(this); } cnct_method = WEBUI_METHOD_POST; retcd = webu_post->processor_init(); } else { cnct_method = WEBUI_METHOD_GET; retcd = MHD_YES; } return retcd; } hostname_get(); if (mystreq(method,"POST")) { retcd = webu_post->processor_start(upload_data, upload_data_size); } else { answer_get(); retcd = MHD_YES; } return retcd; } cls_webu_ans::cls_webu_ans(ctx_motapp *p_motapp, const char *uri) { app = p_motapp; webu = p_motapp->webu; char *tmplang; url = ""; uri_camid = ""; uri_cmd1 = ""; uri_cmd2 = ""; uri_cmd3 = ""; clientip = ""; lang = ""; /* Two digit lang code */ auth_opaque = (char*)mymalloc(WEBUI_LEN_PARM); auth_realm = (char*)mymalloc(WEBUI_LEN_PARM); auth_user = nullptr; /* Buffer to hold the user name*/ auth_pass = nullptr; /* Buffer to hold the password */ authenticated = false; /* boolean for whether we are authenticated*/ resp_page = ""; /* The response being constructed */ req_file = nullptr; cnct_type = WEBUI_CNCT_UNKNOWN; resp_type = WEBUI_RESP_HTML; /* Default to html response */ cnct_method = WEBUI_METHOD_GET; camindx = -1; device_id = -1; tmplang = setlocale(LC_ALL, NULL); if (tmplang == nullptr) { lang = "en"; } else { lang.assign(tmplang, 2); } mhd_first = true; cam = nullptr; webu_file = nullptr; webu_html = nullptr; webu_json = nullptr; webu_post = nullptr; webu_stream = nullptr; url.assign(uri); parms_edit(); webu->cnct_cnt++; } void cls_webu_ans::deinit_counter() { ctx_stream_data *strm; cls_camera *p_cam; int indx, cam_max, cam_min; if (cnct_type < WEBUI_CNCT_JPG_MIN) { return; } if (device_id == 0) { cam_min = 0; cam_max = app->cam_cnt; } else if ((device_id > 0) && (camindx >= 0)) { cam_min = camindx; cam_max = cam_min +1; } else { cam_min = 1; cam_max = 0; } for (indx=cam_min; indxcam_list[indx]; pthread_mutex_lock(&p_cam->stream.mutex); if ((cnct_type == WEBUI_CNCT_JPG_FULL) || (cnct_type == WEBUI_CNCT_TS_FULL)) { strm = &p_cam->stream.norm; } else if ( (cnct_type == WEBUI_CNCT_JPG_SUB) || (cnct_type == WEBUI_CNCT_TS_SUB)) { strm = &p_cam->stream.sub; } else if ( (cnct_type == WEBUI_CNCT_JPG_MOTION) || (cnct_type == WEBUI_CNCT_TS_MOTION )) { strm = &p_cam->stream.motion; } else if ( (cnct_type == WEBUI_CNCT_JPG_SOURCE) || (cnct_type == WEBUI_CNCT_TS_SOURCE)) { strm = &p_cam->stream.source; } else if ( (cnct_type == WEBUI_CNCT_JPG_SECONDARY) || (cnct_type == WEBUI_CNCT_TS_SECONDARY)) { strm = &p_cam->stream.secondary; } else { strm = &p_cam->stream.norm; } if ((cnct_type > WEBUI_CNCT_JPG_MIN) && (cnct_type < WEBUI_CNCT_JPG_MAX)) { if ((device_id == 0) && (strm->all_cnct > 0)) { strm->all_cnct--; } else if ((device_id > 0) && (strm->jpg_cnct > 0)) { strm->jpg_cnct--; } } else if ((cnct_type > WEBUI_CNCT_TS_MIN) && (cnct_type < WEBUI_CNCT_TS_MAX)) { if ((device_id == 0) && (strm->all_cnct > 0)) { strm->all_cnct--; } else if ((device_id > 0) && (strm->ts_cnct > 0)) { strm->ts_cnct--; } } if ((strm->all_cnct == 0) && (strm->jpg_cnct == 0) && (strm->ts_cnct == 0) && (p_cam->passflag)) { myfree(strm->img_data); myfree(strm->jpg_data); } pthread_mutex_unlock(&p_cam->stream.mutex); } } cls_webu_ans::~cls_webu_ans() { deinit_counter(); if (webu_file != nullptr) { delete webu_file; } if (webu_html != nullptr) { delete webu_html; } if (webu_json != nullptr) { delete webu_json; } if (webu_post != nullptr) { delete webu_post; } if (webu_stream != nullptr) { delete webu_stream; } myfree(auth_user); myfree(auth_pass); myfree(auth_opaque); myfree(auth_realm); webu->cnct_cnt--; }