Files
motion/src/webu.cpp
2021-03-21 16:23:51 -06:00

2118 lines
76 KiB
C++

/*
* webu.cpp
*
* Webcontrol and Streams for motion.
*
* This software is distributed under the GNU Public License Version 2
* See also the file 'COPYING'.
*
* Portions of code from Angel Carpintero (motiondevelop@gmail.com)
* from webhttpd.c Copyright 2004-2005
*
* Majority of module written by MrDave.
*
* Function naming scheme:
* webu* - All functions in this module have this prefix.
* webu_start - Entry point to start the daemon.
* webu_stop - Entry point to stop the daemon
* webu_mhd* - Functions related to libmicrohttd implementation
* webu_process_action - Performs most items under the action menu
* webu_process_config - Saves the parameter values into Motion.
* webu_process_track - Performs the tracking functions.
*
* Some function names are long and are not expected to contain any
* logger message that would display the function name to the user.
*
* Functions are generally kept to under one page in length
*
* Known Issues:
* The quit/restart uses signals and this should be reconsidered.
* The tracking is "best effort" since developer does not have tracking camera.
* The conf_cmdparse assumes that the pointers to the motion context for each
* camera are always sequential and enforcement of the pointers being sequential
* has not been observed in the other modules. (This is a legacy assumption)
*/
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include "motion.hpp"
#include "conf.hpp"
#include "logger.hpp"
#include "util.hpp"
#include "webu.hpp"
#include "webu_html.hpp"
#include "webu_text.hpp"
#include "webu_stream.hpp"
#include "track.hpp"
/* Context to pass the parms to functions to start mhd */
struct mhdstart_ctx {
struct ctx_cam **camlst;
char *tls_cert;
char *tls_key;
int ctrl;
int indxthrd;
struct MHD_OptionItem *mhd_ops;
int mhd_opt_nbr;
unsigned int mhd_flags;
int ipv6;
struct sockaddr_in lpbk_ipv4;
struct sockaddr_in6 lpbk_ipv6;
};
static void webu_context_init(struct ctx_cam **camlst, struct ctx_cam *cam, struct webui_ctx *webui) {
int indx;
webui->url = (char*)mymalloc(WEBUI_LEN_URLI);
webui->uri_camid = (char*)mymalloc(WEBUI_LEN_PARM);
webui->uri_cmd1 = (char*)mymalloc(WEBUI_LEN_PARM);
webui->uri_cmd2 = (char*)mymalloc(WEBUI_LEN_PARM);
webui->uri_parm1 = (char*)mymalloc(WEBUI_LEN_PARM);
webui->uri_value1 = (char*)mymalloc(WEBUI_LEN_PARM);
webui->uri_parm2 = (char*)mymalloc(WEBUI_LEN_PARM);
webui->uri_value2 = (char*)mymalloc(WEBUI_LEN_PARM);
webui->clientip = (char*)mymalloc(WEBUI_LEN_URLI);
webui->hostname = (char*)mymalloc(WEBUI_LEN_PARM);
webui->auth_denied = (char*)mymalloc(WEBUI_LEN_RESP);
webui->auth_opaque = (char*)mymalloc(WEBUI_LEN_PARM);
webui->auth_realm = (char*)mymalloc(WEBUI_LEN_PARM);
webui->text_eol = (char*)mymalloc(WEBUI_LEN_PARM);
webui->auth_user = NULL; /* Buffer to hold the user name*/
webui->auth_pass = NULL; /* Buffer to hold the password */
webui->authenticated = FALSE; /* boolean for whether we are authenticated*/
webui->lang = (char*)mymalloc(3); /* Two digit lang code plus null terminator */
webui->lang_full = (char*)mymalloc(6); /* lang code, e.g US_en */
webui->resp_size = WEBUI_LEN_RESP * 10; /* The size of the resp_page buffer. May get adjusted */
webui->resp_used = 0; /* How many bytes used so far in resp_page*/
webui->stream_pos = 0; /* Stream position of image being sent */
webui->stream_fps = 1; /* Stream rate */
webui->resp_page = (char*)mymalloc(webui->resp_size); /* The response being constructed */
webui->camlst = camlst; /* The list of context's for all cameras */
webui->cam = cam; /* The context pointer for a single camera */
webui->cnct_type = WEBUI_CNCT_UNKNOWN;
/* get the number of cameras and threads */
indx = 0;
if (webui->camlst != NULL){
while (webui->camlst[++indx]);
}
webui->cam_threads = indx;
webui->cam_count = indx;
if (indx > 1)
webui->cam_count--;
/* 1 thread, 1 camera = just motion.conf.
* 2 thread, 1 camera, then using motion.conf plus a separate camera file */
snprintf(webui->lang_full, 6,"%s", getenv("LANGUAGE"));
snprintf(webui->lang, 3,"%s",webui->lang_full);
memset(webui->hostname,'\0',WEBUI_LEN_PARM);
memset(webui->resp_page,'\0',webui->resp_size);
return;
}
static void webu_context_null(struct webui_ctx *webui) {
/* Null out all the pointers in our webui context */
webui->url = NULL;
webui->hostname = NULL;
webui->uri_camid = NULL;
webui->uri_cmd1 = NULL;
webui->uri_cmd2 = NULL;
webui->uri_parm1 = NULL;
webui->uri_value1 = NULL;
webui->uri_parm2 = NULL;
webui->uri_value2 = NULL;
webui->lang = NULL;
webui->lang_full = NULL;
webui->resp_page = NULL;
webui->connection = NULL;
webui->auth_user = NULL;
webui->auth_pass = NULL;
webui->auth_denied = NULL;
webui->auth_opaque = NULL;
webui->auth_realm = NULL;
webui->clientip = NULL;
webui->text_eol = NULL;
return;
}
static void webu_context_free(struct webui_ctx *webui) {
if (webui->hostname != NULL) free(webui->hostname);
if (webui->url != NULL) free(webui->url);
if (webui->uri_camid != NULL) free(webui->uri_camid);
if (webui->uri_cmd1 != NULL) free(webui->uri_cmd1);
if (webui->uri_cmd2 != NULL) free(webui->uri_cmd2);
if (webui->uri_parm1 != NULL) free(webui->uri_parm1);
if (webui->uri_value1 != NULL) free(webui->uri_value1);
if (webui->uri_parm2 != NULL) free(webui->uri_parm2);
if (webui->uri_value2 != NULL) free(webui->uri_value2);
if (webui->lang != NULL) free(webui->lang);
if (webui->lang_full != NULL) free(webui->lang_full);
if (webui->resp_page != NULL) free(webui->resp_page);
if (webui->auth_user != NULL) free(webui->auth_user);
if (webui->auth_pass != NULL) free(webui->auth_pass);
if (webui->auth_denied != NULL) free(webui->auth_denied);
if (webui->auth_opaque != NULL) free(webui->auth_opaque);
if (webui->auth_realm != NULL) free(webui->auth_realm);
if (webui->clientip != NULL) free(webui->clientip);
if (webui->text_eol != NULL) free(webui->text_eol);
webu_context_null(webui);
free(webui);
return;
}
static void webu_badreq(struct webui_ctx *webui){
/* This function is used in this webu module as a central function when there is a bad
* request. Since sometimes we will be unable to determine what camera context (stream
* or camera) originated the request and we have NULL for camlist and cam, we default the
* response to be HTML. Otherwise, we do know the type and we send back to the user the
* bad request response either with or without the HTML tags.
*/
if (webui->cam != NULL) {
if (webui->cam->conf->webcontrol_interface == 1){
webu_text_badreq(webui);
} else {
webu_html_badreq(webui);
}
} else if (webui->camlst != NULL) {
if (webui->camlst[0]->conf->webcontrol_interface == 1){
webu_text_badreq(webui);
} else {
webu_html_badreq(webui);
}
} else {
webu_html_badreq(webui);
}
}
void webu_write(struct webui_ctx *webui, const char *buf) {
/* Copy the buf data to our response buffer. If the response buffer is not large enough to
* accept our new data coming in, then expand it in chunks of 10
*/
int resp_len;
char *temp_resp;
size_t temp_size;
resp_len = strlen(buf);
temp_size = webui->resp_size;
while ((resp_len + webui->resp_used) > temp_size){
temp_size = temp_size + (WEBUI_LEN_RESP * 10);
}
if (temp_size > webui->resp_size){
temp_resp = (char*)mymalloc(webui->resp_size);
memcpy(temp_resp, webui->resp_page, webui->resp_size);
free(webui->resp_page);
webui->resp_page = (char*)mymalloc(temp_size);
memset(webui->resp_page,'\0',temp_size);
memcpy(webui->resp_page, temp_resp, webui->resp_size);
webui->resp_size = temp_size;
free(temp_resp);
}
memcpy(webui->resp_page + webui->resp_used, buf, resp_len);
webui->resp_used = webui->resp_used + resp_len;
return;
}
static int webu_url_decode(char *urlencoded, size_t length) {
/* We are sent a URI encoded string and this decodes it to characters
* If the sent URL that isn't valid, then we clear out the URL
* so it is not processed in further functions. The "answer" functions
* look for empty urls and answer with bad request
*/
char *data = urlencoded;
char *urldecoded = urlencoded;
int scan_rslt;
size_t origlen;
origlen = length;
if (urlencoded[0] != '/'){
MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid url: %s"),urlencoded);
memset(urlencoded,'\0',origlen);
return -1;
}
while (length > 0) {
if (*data == '%') {
char c[3];
int i;
data++;
length--;
c[0] = *data++;
length--;
c[1] = *data;
c[2] = 0;
scan_rslt = sscanf(c, "%x", &i);
if (scan_rslt < 1){
MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO,_("Error decoding url"));
memset(urlencoded,'\0',origlen);
return -1;
}
if (i < 128) {
*urldecoded++ = (char)i;
} else {
*urldecoded++ = '%';
*urldecoded++ = c[0];
*urldecoded++ = c[1];
}
} else if (*data == '<' || *data == '+' || *data == '>') {
*urldecoded++ = ' ';
} else {
*urldecoded++ = *data;
}
data++;
length--;
}
*urldecoded = '\0';
return 0;
}
static void webu_parms_edit(struct webui_ctx *webui) {
/* Determine the thread number provided.
* If no thread provided, assign it to -1
* Samples:
* http://localhost:8081/0/stream (camlist will be populated and this function will set cam)
* http://localhost:8081/stream (camlist will be null, cam will be populated)
* http://localhost:8081/ (camlist will be null, cam will be populated)
*/
int indx, is_nbr;
if (strlen(webui->uri_camid) > 0){
is_nbr = TRUE;
for (indx=0; indx < (int)strlen(webui->uri_camid);indx++){
if ((webui->uri_camid[indx] > '9') || (webui->uri_camid[indx] < '0')) is_nbr = FALSE;
}
if (is_nbr){
webui->thread_nbr = atoi(webui->uri_camid);
} else {
webui->thread_nbr = -1;
}
} else {
webui->thread_nbr = -1;
}
/* Set the single context pointer to thread we are answering
* If the connection is for a single stream (legacy method of a port
* per stream), then the camlist will be null and the camera context
* will already be assigned into webui->cam. This is part of the
* init function which is called for MHD and it has the different
* variations depending upon how the port and cameras were specified.
* Also set/convert the camid into the thread number.
*/
if (webui->camlst != NULL){
if (webui->thread_nbr < 0){
webui->cam = webui->camlst[0];
webui->thread_nbr = 0;
} else {
indx = 0;
while (webui->camlst[indx] != NULL){
if (webui->camlst[indx]->camera_id == webui->thread_nbr){
webui->thread_nbr = indx;
break;
}
indx++;
}
/* This may be null, in which case we will not answer the request */
webui->cam = webui->camlst[indx];
}
}
}
static void webu_parseurl_parms(struct webui_ctx *webui, char *st_pos) {
/* Parse the parameters of the URI
* Earlier functions have assigned the st_pos to the slash after the action and it is
* pointing at the set/get when this function is invoked.
* Samples (MHD takes off the IP:port)
* /{camid}/config/set?{parm}={value1}
* /{camid}/config/get?query={parm}
* /{camid}/track/set?x={value1}&y={value2}
* /{camid}/track/set?pan={value1}&tilt={value2}
* /{camid}/{cmd1}/{cmd2}?{parm1}={value1}&{parm2}={value2}
*/
int parm_len, last_parm;
char *en_pos;
/* First parse out the "set","get","pan","tilt","x","y"
* from the uri and put them into the cmd2.
* st_pos is at the beginning of the command
* If there is no ? then we are done parsing
* Note that each section is looking for a different
* delimitter. (?, =, &, =, &)
*/
last_parm = FALSE;
en_pos = strstr(st_pos,"?");
if (en_pos != NULL){
parm_len = en_pos - st_pos + 1;
if (parm_len >= WEBUI_LEN_PARM) return;
snprintf(webui->uri_cmd2, parm_len,"%s", st_pos);
/* Get the parameter name */
st_pos = st_pos + parm_len; /* Move past the command */
en_pos = strstr(st_pos,"=");
if (en_pos == NULL){
parm_len = strlen(webui->url) - parm_len;
last_parm = TRUE;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return;
snprintf(webui->uri_parm1, parm_len,"%s", st_pos);
if (!last_parm){
/* Get the parameter value */
st_pos = st_pos + parm_len; /* Move past the equals sign */
en_pos = strstr(st_pos,"&");
if (en_pos == NULL){
parm_len = strlen(webui->url) - parm_len;
last_parm = TRUE;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return;
snprintf(webui->uri_value1, parm_len,"%s", st_pos);
}
if (!last_parm){
/* Get the next parameter name */
st_pos = st_pos + parm_len; /* Move past the previous command */
en_pos = strstr(st_pos,"=");
if (en_pos == NULL){
parm_len = strlen(webui->url) - parm_len;
last_parm = TRUE;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return;
snprintf(webui->uri_parm2, parm_len,"%s", st_pos);
}
if (!last_parm){
/* Get the next parameter value */
st_pos = st_pos + parm_len; /* Move past the equals sign */
if (!strcasecmp(webui->uri_parm1,"x") || !strcasecmp(webui->uri_parm1,"pan") ){
en_pos = strstr(st_pos,"&");
} else {
en_pos = NULL;
}
if (en_pos == NULL){
parm_len = strlen(webui->url) - parm_len;
last_parm = TRUE;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return;
snprintf(webui->uri_value2, parm_len,"%s", st_pos);
}
}
}
static void webu_parseurl_reset(struct webui_ctx *webui) {
/* Reset the variables to empty strings*/
memset(webui->uri_camid,'\0',WEBUI_LEN_PARM);
memset(webui->uri_cmd1,'\0',WEBUI_LEN_PARM);
memset(webui->uri_cmd2,'\0',WEBUI_LEN_PARM);
memset(webui->uri_parm1,'\0',WEBUI_LEN_PARM);
memset(webui->uri_value1,'\0',WEBUI_LEN_PARM);
memset(webui->uri_parm2,'\0',WEBUI_LEN_PARM);
memset(webui->uri_value2,'\0',WEBUI_LEN_PARM);
}
static int webu_parseurl(struct webui_ctx *webui) {
/* Parse the sent URI into the commands and parameters
* so we can check the resulting strings in later functions
* and determine what actions to take.
* Samples
* /
* /{camid}
* /{camid}/config/set?log_level=6
* /{camid}/config/set?{parm}={value1}
* /{camid}/config/get?query={parm}
* /{camid}/track/set?x={value1}&y={value2}
* /{camid}/track/set?pan={value1}&tilt={value2}
* /{camid}/{cmd1}/{cmd2}?{parm1}={value1}&{parm2}={value2}
*/
int retcd, parm_len, last_slash;
char *st_pos, *en_pos;
retcd = 0;
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO, _("Sent url: %s"),webui->url);
webu_parseurl_reset(webui);
if (strlen(webui->url) == 0) return -1;
retcd = webu_url_decode(webui->url, strlen(webui->url));
if (retcd != 0) return retcd;
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO, _("Decoded url: %s"),webui->url);
/* Home page */
if (strlen(webui->url) == 1) return 0;
last_slash = 0;
/* Get the camid number and which sometimes this will contain an action if the user
* is setting the port for a particular camera and requests the
* stream by using http://localhost:port/stream
*/
st_pos = webui->url + 1; /* Move past the first "/" */
if (*st_pos == '-') return -1; /* Never allow a negative number */
en_pos = strstr(st_pos,"/");
if (en_pos == NULL){
parm_len = strlen(webui->url);
last_slash = 1;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return -1; /* var was malloc'd to WEBUI_LEN_PARM */
snprintf(webui->uri_camid, parm_len,"%s", st_pos);
if (!last_slash){
/* Get cmd1 or action */
st_pos = st_pos + parm_len; /* Move past the camid */
en_pos = strstr(st_pos,"/");
if (en_pos == NULL){
parm_len = strlen(webui->url) - parm_len ;
last_slash = 1;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return -1; /* var was malloc'd to WEBUI_LEN_PARM */
snprintf(webui->uri_cmd1, parm_len,"%s", st_pos);
}
if (!last_slash){
/* Get cmd2 or action */
st_pos = st_pos + parm_len; /* Move past the first command */
en_pos = strstr(st_pos,"/");
if (en_pos == NULL){
parm_len = strlen(webui->url) - parm_len;
last_slash = 1;
} else {
parm_len = en_pos - st_pos + 1;
}
if (parm_len >= WEBUI_LEN_PARM) return -1; /* var was malloc'd to WEBUI_LEN_PARM */
snprintf(webui->uri_cmd2, parm_len,"%s", st_pos);
}
if ((mystreq(webui->uri_cmd1,"config") ||
mystreq(webui->uri_cmd1,"track") ) &&
(strlen(webui->uri_cmd2) > 0)) {
webu_parseurl_parms(webui, st_pos);
}
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO,
"camid: >%s< cmd1: >%s< cmd2: >%s< parm1:>%s< val1:>%s< parm2:>%s< val2:>%s<"
,webui->uri_camid
,webui->uri_cmd1, webui->uri_cmd2
,webui->uri_parm1, webui->uri_value1
,webui->uri_parm2, webui->uri_value2);
return retcd;
}
void webu_process_action(struct webui_ctx *webui) {
/* Process the actions from the webcontrol that the user requested. This is used
* for both the html and text interface. The text interface just adds a additional
* response whereas the html just performs the action
*/
int indx;
indx = 0;
if ((mystreq(webui->uri_cmd2,"makemovie")) ||
(mystreq(webui->uri_cmd2,"eventend"))) {
if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
while (webui->camlst[++indx]){
webui->camlst[indx]->event_stop = TRUE;
}
} else {
webui->cam->event_stop = TRUE;
}
} else if (mystreq(webui->uri_cmd2,"eventstart")){
if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
while (webui->camlst[++indx]){
webui->camlst[indx]->event_user = TRUE;
}
} else {
webui->cam->event_user = TRUE;
}
} else if (mystreq(webui->uri_cmd2,"snapshot")){
if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
while (webui->camlst[++indx])
webui->camlst[indx]->snapshot = 1;
} else {
webui->cam->snapshot = 1;
}
} else if (mystreq(webui->uri_cmd2,"restart")){
if (webui->thread_nbr == 0) {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Restarting all threads"));
kill(getpid(),SIGHUP);
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,
_("Restarting thread %d"),webui->thread_nbr);
webui->cam->restart_cam = TRUE;
if (webui->cam->running_cam) {
webui->cam->event_stop = TRUE;
webui->cam->finish_cam = TRUE;
}
}
} else if (mystreq(webui->uri_cmd2,"quit")){
if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
while (webui->camlst[++indx]){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,
_("Quitting thread %d"),webui->thread_nbr);
webui->camlst[indx]->restart_cam = FALSE;
webui->camlst[indx]->event_stop = TRUE;
webui->camlst[indx]->event_user = TRUE;
webui->camlst[indx]->finish_cam = TRUE;
}
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,
_("Quitting thread %d"),webui->thread_nbr);
webui->cam->restart_cam = FALSE;
webui->cam->event_stop = TRUE;
webui->cam->event_user = TRUE;
webui->cam->finish_cam = TRUE;
}
} else if (mystreq(webui->uri_cmd2,"end")){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Motion terminating"));
while (webui->camlst[indx]){
webui->camlst[indx]->restart_cam = FALSE;
webui->camlst[indx]->event_stop = TRUE;
webui->camlst[indx]->event_user = TRUE;
webui->camlst[indx]->finish_cam = TRUE;
indx++;
}
} else if (mystreq(webui->uri_cmd2,"start")){
if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
do {
webui->camlst[indx]->pause = 0;
} while (webui->camlst[++indx]);
} else {
webui->cam->pause = 0;
}
} else if (mystreq(webui->uri_cmd2,"pause")){
if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
do {
webui->camlst[indx]->pause = 1;
} while (webui->camlst[++indx]);
} else {
webui->cam->pause = 1;
}
} else if (mystreq(webui->uri_cmd2,"connection")){
webu_text_connection(webui);
} else if (mystreq(webui->uri_cmd2,"status")){
webu_text_status(webui);
} else if ((mystreq(webui->uri_cmd2,"write")) ||
(mystreq(webui->uri_cmd2,"writeyes"))){
conf_parms_write(webui->camlst);
} else {
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO,
_("Invalid action requested: >%s< >%s< >%s<")
, webui->uri_camid, webui->uri_cmd1, webui->uri_cmd2);
return;
}
}
static int webu_process_config_set(struct webui_ctx *webui) {
/* Process the request to change the configuration parameters. Used
* both the html and text interfaces. If the parameter was found, then
* we return 0 otherwise a -1 to tell the calling function whether it
* was a valid parm to change.
*/
int indx, retcd;
char temp_name[WEBUI_LEN_PARM];
/* Search through the depreciated parms and if applicable,
* get the new parameter name so we can check its webcontrol_parms level
*/
snprintf(temp_name, WEBUI_LEN_PARM, "%s", webui->uri_parm1);
indx=0;
while (config_parms_depr[indx].parm_name != "") {
if (mystreq(config_parms_depr[indx].parm_name.c_str() ,webui->uri_parm1)){
snprintf(temp_name, WEBUI_LEN_PARM, "%s", config_parms_depr[indx].newname.c_str());
break;
}
indx++;
}
/* Ignore any request to change an option that is designated above the
* webcontrol_parms level.
*/
indx=0;
while (config_parms[indx].parm_name != "") {
if (((webui->thread_nbr != 0) && (config_parms[indx].main_thread)) ||
(config_parms[indx].webui_level > webui->camlst[0]->conf->webcontrol_parms) ||
(config_parms[indx].webui_level == WEBUI_LEVEL_NEVER) ) {
indx++;
continue;
}
if (mystreq(temp_name, config_parms[indx].parm_name.c_str())) break;
indx++;
}
/* If we found the parm, assign it. If the loop above did not find the parm
* then we ignore the request
*/
if (config_parms[indx].parm_name != ""){
if (strlen(webui->uri_value1) > 0){
conf_edit_set(webui->camlst[webui->thread_nbr]->motapp,webui->thread_nbr
,config_parms[indx].parm_name, webui->uri_value1);
/*If we are updating vid parms, set the flag to update the device.*/
if ((config_parms[indx].parm_name == "vid_control_params") &&
(webui->camlst[webui->thread_nbr]->vdev != NULL)){
webui->camlst[webui->thread_nbr]->vdev->update_parms = TRUE;
}
/* If changing language, do it now */
if (config_parms[indx].parm_name == "native_language"){
if (webui->camlst[webui->thread_nbr]->motapp->native_language){
mytranslate_text("", 1);
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO,_("Native Language : on"));
} else {
mytranslate_text("", 0);
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO,_("Native Language : off"));
}
}
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,_("Set the value to null/zero"));
}
retcd = 0;
} else {
retcd = -1;
}
return retcd;
}
int webu_process_config(struct webui_ctx *webui) {
int retcd;
retcd = 0;
if ((mystreq(webui->uri_cmd1,"config")) &&
(mystreq(webui->uri_cmd2,"set"))) {
retcd = webu_process_config_set(webui);
} else if ((mystreq(webui->uri_cmd1,"config")) &&
(mystreq(webui->uri_cmd2,"get"))) {
webu_text_get_query(webui);
} else if ((mystreq(webui->uri_cmd1,"config")) &&
(mystreq(webui->uri_cmd2,"list"))) {
webu_text_list(webui);
} else {
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO,
_("Invalid action requested: >%s< >%s< >%s<")
, webui->uri_camid, webui->uri_cmd1, webui->uri_cmd2);
}
return retcd;
}
int webu_process_track(struct webui_ctx *webui) {
/* Call the tracking move functions as requested */
struct ctx_coord cent;
int retcd;
if (mystreq(webui->uri_cmd2, "center")) {
webui->camlst[webui->thread_nbr]->frame_skip = track_center(webui->camlst[webui->thread_nbr], 0, 1, 0, 0);
retcd = 0;
} else if (mystreq(webui->uri_cmd2, "set")) {
if (mystreq(webui->uri_parm1, "pan")) {
cent.width = webui->camlst[webui->thread_nbr]->imgs.width;
cent.height = webui->camlst[webui->thread_nbr]->imgs.height;
cent.x = atoi(webui->uri_value1);
cent.y = 0;
webui->camlst[webui->thread_nbr]->frame_skip = track_move(webui->camlst[webui->thread_nbr]
,webui->camlst[webui->thread_nbr]->video_dev
,&cent, &webui->camlst[webui->thread_nbr]->imgs, 1);
cent.width = webui->camlst[webui->thread_nbr]->imgs.width;
cent.height = webui->camlst[webui->thread_nbr]->imgs.height;
cent.x = 0;
cent.y = atoi(webui->uri_value2);
webui->camlst[webui->thread_nbr]->frame_skip = track_move(webui->camlst[webui->thread_nbr]
,webui->camlst[webui->thread_nbr]->video_dev
,&cent, &webui->camlst[webui->thread_nbr]->imgs, 1);
retcd = 0;
} else if (mystrceq(webui->uri_parm1, "x")) {
webui->camlst[webui->thread_nbr]->frame_skip = track_center(webui->camlst[webui->thread_nbr]
, webui->camlst[webui->thread_nbr]->video_dev, 1
, atoi(webui->uri_value1), atoi(webui->uri_value2));
retcd = 0;
} else {
retcd = -1;
}
} else {
retcd = -1;
}
return retcd;
}
static void webu_clientip(struct webui_ctx *webui) {
/* Extract the IP of the client that is connecting. When the
* user specifies Motion to use IPV6 and a IPV4 address comes to us
* the IPv4 address is prepended with a ::ffff: We then trim that off
* so we don't confuse our users.
*/
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 (webui->cam != NULL ){
if (webui->cam->conf->webcontrol_ipv6) is_ipv6 = TRUE;
} else {
if (webui->camlst[0]->conf->webcontrol_ipv6) is_ipv6 = TRUE;
}
con_info = MHD_get_connection_info(webui->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){
snprintf(webui->clientip, WEBUI_LEN_URLI, "%s", "Unknown");
} else {
if (strncmp(client,"::ffff:",7) == 0){
snprintf(webui->clientip, WEBUI_LEN_URLI, "%s", client + 7);
} else {
snprintf(webui->clientip, WEBUI_LEN_URLI, "%s", client);
}
}
} 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){
snprintf(webui->clientip, WEBUI_LEN_URLI, "%s", "Unknown");
} else {
snprintf(webui->clientip,WEBUI_LEN_URLI,"%s",client);
}
}
MOTION_LOG(INF,TYPE_ALL, NO_ERRNO, _("Connection from: %s"),webui->clientip);
}
static void webu_hostname(struct webui_ctx *webui, int ctrl) {
/* use the hostname the browser used to connect to us when
* constructing links to the stream ports. If available
* (which it is in all modern browsers) it is more likely to
* work than the result of gethostname(), which is reliant on
* the machine we're running on having it's hostname setup
* correctly and corresponding DNS in place. */
const char *hdr;
char *en_pos;
int host_len;
hdr = MHD_lookup_connection_value (webui->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_HOST);
if (hdr != NULL){
snprintf(webui->hostname, WEBUI_LEN_PARM, "%s", hdr);
/* IPv6 addresses have :'s in them so special case them */
if (webui->hostname[0] == '['){
en_pos = strstr(webui->hostname, "]");
if (en_pos != NULL){
host_len = en_pos - webui->hostname + 2;
snprintf(webui->hostname, host_len, "%s", hdr);
}
} else {
en_pos = strstr(webui->hostname, ":");
if (en_pos != NULL){
host_len = en_pos - webui->hostname + 1;
snprintf(webui->hostname, host_len, "%s", hdr);
}
}
} else {
gethostname(webui->hostname, WEBUI_LEN_PARM - 1);
}
/* Assign the type of protocol that is associated with the host
* so we can use this protocol as we are building the html page or
* streams.
*/
if (ctrl){
if (webui->cam->conf->webcontrol_tls){
snprintf(webui->hostproto,6,"%s","https");
} else {
snprintf(webui->hostproto,6,"%s","http");
}
} else {
if (webui->cam->conf->stream_tls){
snprintf(webui->hostproto,6,"%s","https");
} else {
snprintf(webui->hostproto,6,"%s","http");
}
}
return;
}
static int webu_mhd_digest_fail(struct webui_ctx *webui,int signal_stale) {
/* Create a denied response to user*/
struct MHD_Response *response;
int retcd;
webui->authenticated = FALSE;
response = MHD_create_response_from_buffer(strlen(webui->auth_denied)
,(void *)webui->auth_denied, MHD_RESPMEM_PERSISTENT);
if (response == NULL) return MHD_NO;
retcd = MHD_queue_auth_fail_response(webui->connection, webui->auth_realm
,webui->auth_opaque, response
,(signal_stale == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO);
MHD_destroy_response(response);
return retcd;
}
static int webu_mhd_digest(struct webui_ctx *webui) {
/* Perform the digest authentication. 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(webui->connection);
if (user == NULL) {
return webu_mhd_digest_fail(webui, MHD_NO);
}
/* Check for valid user name */
if (mystrne(user, webui->auth_user) ){
MOTION_LOG(ALR, TYPE_STREAM, NO_ERRNO
,_("Failed authentication from %s"), webui->clientip);
if (user != NULL) free(user);
return webu_mhd_digest_fail(webui, MHD_NO);
}
if (user != NULL) free(user);
/* Check the password as well*/
retcd = MHD_digest_auth_check(webui->connection, webui->auth_realm
, webui->auth_user, webui->auth_pass, 300);
if (retcd == MHD_NO) {
MOTION_LOG(ALR, TYPE_STREAM, NO_ERRNO
,_("Failed authentication from %s"), webui->clientip);
}
if ( (retcd == MHD_INVALID_NONCE) || (retcd == MHD_NO) ) {
return webu_mhd_digest_fail(webui, retcd);
}
webui->authenticated = TRUE;
return MHD_YES;
}
static int webu_mhd_basic_fail(struct webui_ctx *webui) {
/* Create a denied response to user*/
struct MHD_Response *response;
int retcd;
webui->authenticated = FALSE;
response = MHD_create_response_from_buffer(strlen(webui->auth_denied)
,(void *)webui->auth_denied, MHD_RESPMEM_PERSISTENT);
if (response == NULL) return MHD_NO;
retcd = MHD_queue_basic_auth_fail_response (webui->connection, webui->auth_realm, response);
MHD_destroy_response(response);
return retcd;
}
static int webu_mhd_basic(struct webui_ctx *webui) {
/* Perform Basic Authentication. */
char *user, *pass;
pass = NULL;
user = NULL;
user = MHD_basic_auth_get_username_password (webui->connection, &pass);
if ((user == NULL) || (pass == NULL)){
if (user != NULL) free(user);
if (pass != NULL) free(pass);
return webu_mhd_basic_fail(webui);
}
if ((mystrne(user, webui->auth_user)) || (mystrne(pass, webui->auth_pass))) {
MOTION_LOG(ALR, TYPE_STREAM, NO_ERRNO
,_("Failed authentication from %s"),webui->clientip);
if (user != NULL) free(user);
if (pass != NULL) free(pass);
return webu_mhd_basic_fail(webui);
}
if (user != NULL) free(user);
if (pass != NULL) free(pass);
webui->authenticated = TRUE;
return MHD_YES;
}
static void webu_mhd_auth_parse(struct webui_ctx *webui, int ctrl){
int auth_len;
char *col_pos;
/* Parse apart the user:pass provided*/
if (webui->auth_user != NULL) free(webui->auth_user);
if (webui->auth_pass != NULL) free(webui->auth_pass);
webui->auth_user = NULL;
webui->auth_pass = NULL;
if (ctrl){
auth_len = webui->cam->conf->webcontrol_authentication.length();
col_pos =(char*) strstr(webui->cam->conf->webcontrol_authentication.c_str() ,":");
if (col_pos == NULL){
webui->auth_user = (char*)mymalloc(auth_len+1);
webui->auth_pass = (char*)mymalloc(2);
snprintf(webui->auth_user, auth_len + 1, "%s"
,webui->cam->conf->webcontrol_authentication.c_str());
snprintf(webui->auth_pass, 2, "%s","");
} else {
webui->auth_user = (char*)mymalloc(auth_len - strlen(col_pos) + 1);
webui->auth_pass =(char*)mymalloc(strlen(col_pos));
snprintf(webui->auth_user, auth_len - strlen(col_pos) + 1, "%s"
,webui->cam->conf->webcontrol_authentication.c_str());
snprintf(webui->auth_pass, strlen(col_pos), "%s", col_pos + 1);
}
} else {
auth_len = strlen(webui->cam->conf->stream_authentication.c_str());
col_pos =(char*) strstr(webui->cam->conf->stream_authentication.c_str(),":");
if (col_pos == NULL){
webui->auth_user = (char*)mymalloc(auth_len+1);
webui->auth_pass = (char*)mymalloc(2);
snprintf(webui->auth_user, auth_len + 1, "%s"
,webui->cam->conf->stream_authentication.c_str());
snprintf(webui->auth_pass, 2, "%s","");
} else {
webui->auth_user = (char*)mymalloc(auth_len - strlen(col_pos) + 1);
webui->auth_pass = (char*)mymalloc(strlen(col_pos));
snprintf(webui->auth_user, auth_len - strlen(col_pos) + 1, "%s"
,webui->cam->conf->stream_authentication.c_str());
snprintf(webui->auth_pass, strlen(col_pos), "%s", col_pos + 1);
}
}
}
static int webu_mhd_auth(struct webui_ctx *webui, int ctrl){
/* Set everything up for calling the authentication functions */
unsigned int rand1,rand2;
snprintf(webui->auth_denied, WEBUI_LEN_RESP, "%s"
,"<html><head><title>Access denied</title>"
"</head><body>Access denied</body></html>");
srand(time(NULL));
rand1 = (unsigned int)(42000000.0 * rand() / (RAND_MAX + 1.0));
rand2 = (unsigned int)(42000000.0 * rand() / (RAND_MAX + 1.0));
snprintf(webui->auth_opaque, WEBUI_LEN_PARM, "%08x%08x", rand1, rand2);
snprintf(webui->auth_realm, WEBUI_LEN_PARM, "%s","Motion");
if (ctrl){
/* Authentication for the webcontrol*/
if (webui->cam->conf->webcontrol_authentication == ""){
webui->authenticated = TRUE;
if (webui->cam->conf->webcontrol_auth_method != 0){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("No webcontrol user:pass provided"));
}
return MHD_YES;
}
if (webui->auth_user == NULL) webu_mhd_auth_parse(webui, ctrl);
if (webui->cam->conf->webcontrol_auth_method == 1){
return webu_mhd_basic(webui);
} else if (webui->cam->conf->webcontrol_auth_method == 2){
return webu_mhd_digest(webui);
}
} else {
/* Authentication for the streams */
if (webui->cam->conf->stream_authentication == ""){
webui->authenticated = TRUE;
if (webui->cam->conf->stream_auth_method != 0){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("No stream user:pass provided"));
}
return MHD_YES;
}
if (webui->auth_user == NULL) webu_mhd_auth_parse(webui, ctrl);
if (webui->cam->conf->stream_auth_method == 1) {
return webu_mhd_basic(webui);
} else if (webui->cam->conf->stream_auth_method == 2){
return webu_mhd_digest(webui);
}
}
webui->authenticated = TRUE;
return MHD_YES;
}
static int webu_mhd_send(struct webui_ctx *webui, int ctrl) {
/* Send the response that we created back to the user. Now if the user
* provided a really bad URL, then we couldn't determine which Motion context
* they were wanting. In this situation, we have a webui->cam = NULL and we
* don't know whether it came from a html or text request. In this situation
* we use the MHD defaults and skip adding CORS/Content type. (There isn't any
* Motion context so we can't tell where to look)
* The ctrl parameter is a boolean which just says whether the request is for
* the webcontrol versus stream
*/
int retcd;
struct MHD_Response *response;
response = MHD_create_response_from_buffer (strlen(webui->resp_page)
,(void *)webui->resp_page, MHD_RESPMEM_PERSISTENT);
if (!response){
MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, _("Invalid response"));
return MHD_NO;
}
if (webui->cam != NULL){
if (ctrl){
if (webui->cam->conf->webcontrol_cors_header != ""){
MHD_add_response_header (response, MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
, webui->cam->conf->webcontrol_cors_header.c_str());
}
if (webui->cam->conf->webcontrol_interface == 1){
MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain;");
} else {
MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
}
} else {
if (webui->cam->conf->stream_cors_header !=""){
MHD_add_response_header (response, MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
, webui->cam->conf->stream_cors_header.c_str());
}
MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
}
}
retcd = MHD_queue_response (webui->connection, MHD_HTTP_OK, response);
MHD_destroy_response (response);
return retcd;
}
static void webu_answer_strm_type(struct webui_ctx *webui) {
/* Assign the type of stream that is being answered*/
if ((mystreq(webui->uri_cmd1,"stream")) ||
(mystreq(webui->uri_camid,"stream")) ||
(strlen(webui->uri_camid) == 0)) {
webui->cnct_type = WEBUI_CNCT_FULL;
} else if ((mystreq(webui->uri_cmd1,"substream")) ||
(mystreq(webui->uri_camid,"substream"))){
webui->cnct_type = WEBUI_CNCT_SUB;
} else if ((mystreq(webui->uri_cmd1,"motion")) ||
(mystreq(webui->uri_camid,"motion"))){
webui->cnct_type = WEBUI_CNCT_MOTION;
} else if ((mystreq(webui->uri_cmd1,"source")) ||
(mystreq(webui->uri_camid,"source"))){
webui->cnct_type = WEBUI_CNCT_SOURCE;
} else if ((mystreq(webui->uri_cmd1,"secondary")) ||
(mystreq(webui->uri_camid,"secondary"))){
if (webui->cam->algsec_inuse) {
webui->cnct_type = WEBUI_CNCT_SECONDARY;
} else {
webui->cnct_type = WEBUI_CNCT_UNKNOWN;
}
} else if ((mystreq(webui->uri_cmd1,"current")) ||
(mystreq(webui->uri_camid,"current"))){
webui->cnct_type = WEBUI_CNCT_STATIC;
} else if ((strlen(webui->uri_camid) > 0) &&
(strlen(webui->uri_cmd1) == 0)){
webui->cnct_type = WEBUI_CNCT_FULL;
} else {
webui->cnct_type = WEBUI_CNCT_UNKNOWN;
}
}
static int webu_answer_ctrl(void *cls
, struct MHD_Connection *connection
, const char *url
, const char *method
, const char *version
, const char *upload_data
, size_t *upload_data_size
, void **ptr) {
/* This function "answers" the request for a webcontrol.*/
int retcd;
struct webui_ctx *webui =(struct webui_ctx *) *ptr;
/* Eliminate compiler warnings */
(void)cls;
(void)url;
(void)version;
(void)upload_data;
(void)upload_data_size;
/* Per MHD docs, this is called twice and we should process the second call */
if (webui->mhd_first) {
webui->mhd_first = FALSE;
return MHD_YES;
}
if (mystrne(method, "GET")){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Invalid Method requested: %s"),method);
return MHD_NO;
}
webui->cnct_type = WEBUI_CNCT_CONTROL;
mythreadname_set("wu", 0,NULL);
webui->connection = connection;
/* Throw bad URLS back to user*/
if ((webui->cam == NULL) || (strlen(webui->url) == 0)){
webu_badreq(webui);
retcd = webu_mhd_send(webui, FALSE);
return retcd;
}
if (webui->cam->motapp->webcontrol_finish) return MHD_NO;
if (strlen(webui->clientip) == 0){
webu_clientip(webui);
}
webu_hostname(webui, TRUE);
if (!webui->authenticated) {
retcd = webu_mhd_auth(webui, TRUE);
if (!webui->authenticated) return retcd;
}
if ((webui->camlst[0]->conf->webcontrol_interface == 1) ||
(webui->camlst[0]->conf->webcontrol_interface == 2)) {
webu_text_main(webui);
} else {
webu_html_main(webui);
}
retcd = webu_mhd_send(webui, TRUE);
if (retcd == MHD_NO){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("send page failed %d"),retcd);
}
return retcd;
}
static int webu_answer_strm(void *cls
, struct MHD_Connection *connection
, const char *url
, const char *method
, const char *version
, const char *upload_data
, size_t *upload_data_size
, void **ptr) {
/* Answer the request for all the streams*/
int retcd;
struct webui_ctx *webui =(struct webui_ctx *) *ptr;
/* Eliminate compiler warnings */
(void)cls;
(void)url;
(void)version;
(void)upload_data;
(void)upload_data_size;
/* Per docs, this is called twice and we should process the second call */
if (webui->mhd_first) {
webui->mhd_first = FALSE;
return MHD_YES;
}
if (mystrne(method, "GET")){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Invalid Method requested: %s"),method);
return MHD_NO;
}
mythreadname_set("st", 0,NULL);
webui->connection = connection;
/* Throw bad URLS back to user*/
if ((webui->cam == NULL) || (strlen(webui->url) == 0)){
webu_badreq(webui);
retcd = webu_mhd_send(webui, FALSE);
return retcd;
}
/* Do not answer a request until the motion loop has completed at least once */
if (webui->cam->passflag == 0) return MHD_NO;
if (webui->cam->motapp->webcontrol_finish) return MHD_NO;
if (strlen(webui->clientip) == 0){
webu_clientip(webui);
}
webu_hostname(webui, FALSE);
if (!webui->authenticated) {
retcd = webu_mhd_auth(webui, FALSE);
if (!webui->authenticated) return retcd;
}
webu_answer_strm_type(webui);
retcd = 0;
if (webui->cnct_type == WEBUI_CNCT_STATIC){
retcd = webu_stream_static(webui);
if (retcd == MHD_NO){
webu_badreq(webui);
retcd = webu_mhd_send(webui, FALSE);
}
} else if (webui->cnct_type != WEBUI_CNCT_UNKNOWN) {
retcd = webu_stream_mjpeg(webui);
if (retcd == MHD_NO){
webu_badreq(webui);
retcd = webu_mhd_send(webui, FALSE);
}
} else {
webu_badreq(webui);
retcd = webu_mhd_send(webui, FALSE);
}
if (retcd == MHD_NO){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("send page failed %d"),retcd);
}
return retcd;
}
static void *webu_mhd_init(void *cls, const char *uri, struct MHD_Connection *connection) {
/* This is called at the very start of getting a request before the "answer"
* is processed. There are two variations of this and the difference is how
* we call the webu_context_init. When we are processing for the webcontrol or
* the stream port specified in the motion.conf file, we pass into the init function
* the full list of all the cameras. The other version of the init is used when the
* user specifies a unique port for each camera. In this situation, the full list
* context is passed in as a null and the context of the camera desired is passed
* instead.
* When this function is processed, we basically only have the URL that the user requested
* so we initialize everything and then parse out the URL to determine what the user is
* asking.
*/
struct ctx_cam **camlst =(struct ctx_cam **) cls;
struct webui_ctx *webui;
int retcd;
(void)connection;
/* Set the thread name to connection until we know whether control or stream answers*/
mythreadname_set("cn", 0,NULL);
webui =(struct webui_ctx* ) malloc(sizeof(struct webui_ctx));
webu_context_init(camlst, NULL, webui);
webui->mhd_first = TRUE;
snprintf(webui->url,WEBUI_LEN_URLI,"%s",uri);
retcd = webu_parseurl(webui);
if (retcd != 0){
webu_parseurl_reset(webui);
memset(webui->url,'\0',WEBUI_LEN_URLI);
}
webu_parms_edit(webui);
return webui;
}
static void *webu_mhd_init_one(void *cls, const char *uri, struct MHD_Connection *connection) {
/* This function initializes all the webui variables as we are getting a request. This
* variation of the init is the one used when the user has specified a unique port number
* for each camera. The variation is in how the webu_context_init is invoked. This passes
* in a NULL for the full context list (webui->camlist) and instead assigns the particular
* camera context to webui->cam
*/
struct ctx_cam *cam =(struct ctx_cam *) cls;
struct webui_ctx *webui;
int retcd;
(void)connection;
/* Set the thread name to connection until we know whether control or stream answers*/
mythreadname_set("cn", 0,NULL);
webui =(struct webui_ctx*) malloc(sizeof(struct webui_ctx));
webu_context_init(NULL, cam, webui);
webui->mhd_first = TRUE;
snprintf(webui->url,WEBUI_LEN_URLI,"%s",uri);
retcd = webu_parseurl(webui);
if (retcd != 0){
webu_parseurl_reset(webui);
memset(webui->url,'\0',WEBUI_LEN_URLI);
}
webu_parms_edit(webui);
return webui;
}
static void webu_mhd_deinit(void *cls
, struct MHD_Connection *connection
, void **con_cls
, enum MHD_RequestTerminationCode toe) {
/* This is the function called as the connection is closed so we free our webui variables*/
struct webui_ctx *webui =(struct webui_ctx *) *con_cls;
/* Eliminate compiler warnings */
(void)connection;
(void)cls;
(void)toe;
if (webui->cnct_type == WEBUI_CNCT_FULL ){
pthread_mutex_lock(&webui->cam->stream.mutex);
webui->cam->stream.norm.cnct_count--;
pthread_mutex_unlock(&webui->cam->stream.mutex);
} else if (webui->cnct_type == WEBUI_CNCT_SUB ){
pthread_mutex_lock(&webui->cam->stream.mutex);
webui->cam->stream.sub.cnct_count--;
pthread_mutex_unlock(&webui->cam->stream.mutex);
} else if (webui->cnct_type == WEBUI_CNCT_MOTION ){
pthread_mutex_lock(&webui->cam->stream.mutex);
webui->cam->stream.motion.cnct_count--;
pthread_mutex_unlock(&webui->cam->stream.mutex);
} else if (webui->cnct_type == WEBUI_CNCT_SOURCE ){
pthread_mutex_lock(&webui->cam->stream.mutex);
webui->cam->stream.source.cnct_count--;
pthread_mutex_unlock(&webui->cam->stream.mutex);
} else if (webui->cnct_type == WEBUI_CNCT_SECONDARY ){
pthread_mutex_lock(&webui->cam->stream.mutex);
webui->cam->stream.secondary.cnct_count--;
pthread_mutex_unlock(&webui->cam->stream.mutex);
} else if (webui->cnct_type == WEBUI_CNCT_STATIC ){
pthread_mutex_lock(&webui->cam->stream.mutex);
webui->cam->stream.norm.cnct_count--;
pthread_mutex_unlock(&webui->cam->stream.mutex);
}
webu_context_free(webui);
return;
}
static void webu_mhd_features_basic(struct mhdstart_ctx *mhdst){
/* Use the MHD function to see what features it supports*/
#if MHD_VERSION < 0x00094400
(void)mhdst;
#else
int retcd;
retcd = MHD_is_feature_supported (MHD_FEATURE_BASIC_AUTH);
if (retcd == MHD_YES){
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO ,_("Basic authentication: available"));
} else {
if ((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_auth_method == 1)){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Basic authentication: disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_auth_method = 0;
} else if ((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_auth_method == 1)){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Basic authentication: disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->stream_auth_method = 0;
} else {
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("Basic authentication: disabled"));
}
}
#endif
}
static void webu_mhd_features_digest(struct mhdstart_ctx *mhdst){
/* Use the MHD function to see what features it supports*/
#if MHD_VERSION < 0x00094400
(void)mhdst;
#else
int retcd;
retcd = MHD_is_feature_supported (MHD_FEATURE_DIGEST_AUTH);
if (retcd == MHD_YES){
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO ,_("Digest authentication: available"));
} else {
if ((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_auth_method == 2)){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Digest authentication: disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_auth_method = 0;
} else if ((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_auth_method == 2)){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Digest authentication: disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->stream_auth_method = 0;
} else {
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("Digest authentication: disabled"));
}
}
#endif
}
static void webu_mhd_features_ipv6(struct mhdstart_ctx *mhdst){
/* Use the MHD function to see what features it supports
* If we have a really old version of MHD, then we will just support
* IPv4
*/
#if MHD_VERSION < 0x00094400
if (mhdst->ipv6){
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("libmicrohttpd libary too old ipv6 disabled"));
if (mhdst->ipv6) mhdst->ipv6 = 0;
}
#else
int retcd;
retcd = MHD_is_feature_supported (MHD_FEATURE_IPv6);
if (retcd == MHD_YES){
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO ,_("IPV6: available"));
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("IPV6: disabled"));
if (mhdst->ipv6) mhdst->ipv6 = 0;
}
#endif
}
static void webu_mhd_features_tls(struct mhdstart_ctx *mhdst){
/* Use the MHD function to see what features it supports
* If we have a really old version of MHD, then we will will not
* support the ssl/tls request.
*/
#if MHD_VERSION < 0x00094400
if ((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_tls)){
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("libmicrohttpd libary too old SSL/TLS disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_tls = 0;
} else if ((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_tls)) {
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("libmicrohttpd libary too old SSL/TLS disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->stream_tls = 0;
}
#else
int retcd;
retcd = MHD_is_feature_supported (MHD_FEATURE_SSL);
if (retcd == MHD_YES){
MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO ,_("SSL/TLS: available"));
} else {
if ((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_tls)){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("SSL/TLS: disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_tls = 0;
} else if ((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_tls)){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("SSL/TLS: disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->stream_tls = 0;
} else {
MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("SSL/TLS: disabled"));
}
}
#endif
}
static void webu_mhd_features(struct mhdstart_ctx *mhdst){
/* This function goes through at least a few of the MHD features
* and adjusts the user parameters from the configuration as
* needed to reflect what MHD can do
*/
webu_mhd_features_basic(mhdst);
webu_mhd_features_digest(mhdst);
webu_mhd_features_ipv6(mhdst);
webu_mhd_features_tls(mhdst);
}
static char *webu_mhd_loadfile(const char *fname){
/* This function loads the requested certificate and key files into memory so we
* can use them as needed if the user wants ssl/tls support. If the user did not
* specify a file in the configuration, then we return NULL.
*/
FILE *infile;
size_t file_size, read_size;
char * file_char;
if (fname == NULL) {
file_char = NULL;
} else {
infile = fopen(fname, "rb");
if (infile != NULL){
fseek(infile, 0, SEEK_END);
file_size = ftell(infile);
if (file_size > 0 ){
file_char = (char*)mymalloc(file_size +1);
fseek(infile, 0, SEEK_SET);
read_size = fread(file_char, file_size, 1, infile);
if (read_size > 0 ){
file_char[file_size] = 0;
} else {
free(file_char);
file_char = NULL;
MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO
,_("Error reading file for SSL/TLS support."));
}
} else {
file_char = NULL;
}
fclose(infile);
} else {
file_char = NULL;
}
}
return file_char;
}
static void webu_mhd_checktls(struct mhdstart_ctx *mhdst){
/* This function validates that if the user requested a SSL/TLS connection, then
* they also need to provide a certificate and key file. If those are not provided
* then we revise the configuration request for ssl/tls
*/
if (mhdst->ctrl){
if (mhdst->camlst[0]->conf->webcontrol_tls){
if ((mhdst->camlst[0]->conf->webcontrol_cert == "") || (mhdst->tls_cert == NULL)) {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("SSL/TLS requested but no cert file provided. SSL/TLS disabled"));
mhdst->camlst[0]->conf->webcontrol_tls = 0;
}
if ((mhdst->camlst[0]->conf->webcontrol_key == "") || (mhdst->tls_key == NULL)) {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("SSL/TLS requested but no key file provided. SSL/TLS disabled"));
mhdst->camlst[0]->conf->webcontrol_tls = 0;
}
}
} else {
if (mhdst->camlst[mhdst->indxthrd]->conf->stream_tls){
if ((mhdst->camlst[0]->conf->webcontrol_cert == "") || (mhdst->tls_cert == NULL)) {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("SSL/TLS requested but no cert file provided. SSL/TLS disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->stream_tls = 0;
}
if ((mhdst->camlst[0]->conf->webcontrol_key == "") || (mhdst->tls_key == NULL)) {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("SSL/TLS requested but no key file provided. SSL/TLS disabled"));
mhdst->camlst[mhdst->indxthrd]->conf->stream_tls = 0;
}
}
}
}
static void webu_mhd_opts_init(struct mhdstart_ctx *mhdst){
/* This function sets the init function to use for the MHD connection. If
* the connection is related to the webcontrol or the stream specified in the
* motion.conf file, then we pass in the full context list of all cameras. If
* the MHD connection is only going to be for a single camera (a unique port for
* each camera), then we call a different init function which only wants the single
* motion context for that particular camera.
*/
if ((!mhdst->ctrl) && (mhdst->indxthrd != 0)){
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_URI_LOG_CALLBACK;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = (intptr_t)webu_mhd_init_one;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->camlst[mhdst->indxthrd];
mhdst->mhd_opt_nbr++;
} else {
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_URI_LOG_CALLBACK;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = (intptr_t)webu_mhd_init;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->camlst;
mhdst->mhd_opt_nbr++;
}
}
static void webu_mhd_opts_deinit(struct mhdstart_ctx *mhdst){
/* Set the MHD option on the function to call when the connection closes */
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_NOTIFY_COMPLETED;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = (intptr_t)webu_mhd_deinit;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = NULL;
mhdst->mhd_opt_nbr++;
}
static void webu_mhd_opts_localhost(struct mhdstart_ctx *mhdst){
/* Set the MHD option on the acceptable connections. This is used to handle the
* motion configuation option of localhost only.
*/
if ((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_localhost)){
if (mhdst->ipv6){
memset(&mhdst->lpbk_ipv6, 0, sizeof(struct sockaddr_in6));
mhdst->lpbk_ipv6.sin6_family = AF_INET6;
mhdst->lpbk_ipv6.sin6_port = htons(mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_port);
mhdst->lpbk_ipv6.sin6_addr = in6addr_loopback;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_SOCK_ADDR;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = (struct sosockaddr *)(&mhdst->lpbk_ipv6);
mhdst->mhd_opt_nbr++;
} else {
memset(&mhdst->lpbk_ipv4, 0, sizeof(struct sockaddr_in));
mhdst->lpbk_ipv4.sin_family = AF_INET;
mhdst->lpbk_ipv4.sin_port = htons(mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_port);
mhdst->lpbk_ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_SOCK_ADDR;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = (struct sockaddr *)(&mhdst->lpbk_ipv4);
mhdst->mhd_opt_nbr++;
}
} else if((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_localhost)){
if (mhdst->ipv6){
memset(&mhdst->lpbk_ipv6, 0, sizeof(struct sockaddr_in6));
mhdst->lpbk_ipv6.sin6_family = AF_INET6;
mhdst->lpbk_ipv6.sin6_port = htons(mhdst->camlst[mhdst->indxthrd]->conf->stream_port);
mhdst->lpbk_ipv6.sin6_addr = in6addr_loopback;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_SOCK_ADDR;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = (struct sosockaddr *)(&mhdst->lpbk_ipv6);
mhdst->mhd_opt_nbr++;
} else {
memset(&mhdst->lpbk_ipv4, 0, sizeof(struct sockaddr_in));
mhdst->lpbk_ipv4.sin_family = AF_INET;
mhdst->lpbk_ipv4.sin_port = htons(mhdst->camlst[mhdst->indxthrd]->conf->stream_port);
mhdst->lpbk_ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_SOCK_ADDR;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = (struct sockaddr *)(&mhdst->lpbk_ipv4);
mhdst->mhd_opt_nbr++;
}
}
}
static void webu_mhd_opts_digest(struct mhdstart_ctx *mhdst){
/* Set the MHD option for the type of authentication that we will be using. This
* function is when we are wanting to use digest authentication
*/
if (((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_auth_method == 2)) ||
((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_auth_method == 2))) {
if (mhdst->ctrl) {
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_DIGEST_AUTH_RANDOM;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = sizeof(mhdst->camlst[mhdst->indxthrd]->motapp->webcontrol_digest_rand);
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->camlst[mhdst->indxthrd]->motapp->webcontrol_digest_rand;
mhdst->mhd_opt_nbr++;
} else {
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_DIGEST_AUTH_RANDOM;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = sizeof(mhdst->camlst[mhdst->indxthrd]->stream.digest_rand);
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->camlst[mhdst->indxthrd]->stream.digest_rand;
mhdst->mhd_opt_nbr++;
}
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_NONCE_NC_SIZE;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 300;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = NULL;
mhdst->mhd_opt_nbr++;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_CONNECTION_TIMEOUT;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = (unsigned int) 120;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = NULL;
mhdst->mhd_opt_nbr++;
}
}
static void webu_mhd_opts_tls(struct mhdstart_ctx *mhdst){
/* Set the MHD options needed when we want TLS connections */
if ((( mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_tls)) ||
((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_tls))) {
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_HTTPS_MEM_CERT;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->tls_cert;
mhdst->mhd_opt_nbr++;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_HTTPS_MEM_KEY;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->tls_key;
mhdst->mhd_opt_nbr++;
}
}
static void webu_mhd_opts(struct mhdstart_ctx *mhdst){
/* Set all the options we need based upon the motion configuration parameters*/
mhdst->mhd_opt_nbr = 0;
webu_mhd_checktls(mhdst);
webu_mhd_opts_deinit(mhdst);
webu_mhd_opts_init(mhdst);
webu_mhd_opts_localhost(mhdst);
webu_mhd_opts_digest(mhdst);
webu_mhd_opts_tls(mhdst);
mhdst->mhd_ops[mhdst->mhd_opt_nbr].option = MHD_OPTION_END;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].value = 0;
mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = NULL;
mhdst->mhd_opt_nbr++;
}
static void webu_mhd_flags(struct mhdstart_ctx *mhdst){
/* This sets the MHD startup flags based upon what user put into configuration */
mhdst->mhd_flags = MHD_USE_THREAD_PER_CONNECTION;
if (mhdst->ipv6) mhdst->mhd_flags = mhdst->mhd_flags | MHD_USE_DUAL_STACK;
if ((mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->webcontrol_tls)){
mhdst->mhd_flags = mhdst->mhd_flags | MHD_USE_SSL;
} else if ((!mhdst->ctrl) && (mhdst->camlst[mhdst->indxthrd]->conf->stream_tls)){
mhdst->mhd_flags = mhdst->mhd_flags | MHD_USE_SSL;
}
}
static void webu_strm_ntc(struct ctx_cam **camlst, int indxthrd){
int indx;
if (indxthrd == 0 ){
if (camlst[1] != NULL) {
indx = 1;
while (camlst[indx] != NULL){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Started camera %d stream on port/camera_id %d/%d")
,camlst[indx]->camera_id
,camlst[indxthrd]->conf->stream_port
,camlst[indx]->camera_id);
indx++;
}
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Started camera %d stream on port %d")
,camlst[indxthrd]->camera_id,camlst[indxthrd]->conf->stream_port);
}
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Started camera %d stream on port %d")
,camlst[indxthrd]->camera_id,camlst[indxthrd]->conf->stream_port);
}
}
static void webu_init_ctrl(struct ctx_motapp *motapp){
/* This is the function that actually starts the MHD daemon for handling the webcontrol.
* There are many options for MHD and they will vary depending upon what our Motion user
* has requested in the configuration. There are many functions in this module to assign
* these options and they are passed in a pointer to the mhdst variable so that they can
* assign the correct values for MHD start up. Since this function is doing the webcontrol
* we are only using thread 0 values.
*/
struct mhdstart_ctx mhdst;
unsigned int randnbr;
mhdst.tls_cert = webu_mhd_loadfile(motapp->cam_list[0]->conf->webcontrol_cert.c_str());
mhdst.tls_key = webu_mhd_loadfile(motapp->cam_list[0]->conf->webcontrol_key.c_str());
mhdst.ctrl = TRUE;
mhdst.indxthrd = 0;
mhdst.camlst = motapp->cam_list;
mhdst.ipv6 = motapp->cam_list[0]->conf->webcontrol_ipv6;
/* Set the rand number for webcontrol digest if needed */
srand(time(NULL));
randnbr = (unsigned int)(42000000.0 * rand() / (RAND_MAX + 1.0));
snprintf(motapp->webcontrol_digest_rand
,sizeof(motapp->webcontrol_digest_rand),"%d",randnbr);
motapp->webcontrol_daemon = NULL;
if (motapp->cam_list[0]->conf->webcontrol_port != 0 ){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Starting webcontrol on port %d")
,motapp->cam_list[0]->conf->webcontrol_port);
mhdst.mhd_ops =(struct MHD_OptionItem*)mymalloc(sizeof(struct MHD_OptionItem)*WEBUI_MHD_OPTS);
webu_mhd_features(&mhdst);
webu_mhd_opts(&mhdst);
webu_mhd_flags(&mhdst);
motapp->webcontrol_daemon = MHD_start_daemon (mhdst.mhd_flags
,motapp->cam_list[0]->conf->webcontrol_port
,NULL, NULL
,&webu_answer_ctrl, motapp->cam_list
,MHD_OPTION_ARRAY, mhdst.mhd_ops
,MHD_OPTION_END);
free(mhdst.mhd_ops);
if (motapp->webcontrol_daemon == NULL){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Unable to start MHD"));
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Started webcontrol on port %d")
,motapp->cam_list[0]->conf->webcontrol_port);
}
}
if (mhdst.tls_cert != NULL) free(mhdst.tls_cert);
if (mhdst.tls_key != NULL) free(mhdst.tls_key);
return;
}
static void webu_init_strm(struct ctx_cam **cam_list){
/* This function starts up the daemon for the streams. It loops through
* all of the camera context's provided and starts streams as requested. If
* the thread number is zero, then it starts the full list stream context
*/
struct mhdstart_ctx mhdst;
unsigned int randnbr;
mhdst.tls_cert = webu_mhd_loadfile(cam_list[0]->conf->webcontrol_cert.c_str());
mhdst.tls_key = webu_mhd_loadfile(cam_list[0]->conf->webcontrol_key.c_str());
mhdst.ctrl = FALSE;
mhdst.indxthrd = 0;
mhdst.camlst = cam_list;
mhdst.ipv6 = cam_list[0]->conf->webcontrol_ipv6;
/* Set the rand number for webcontrol digest if needed */
srand(time(NULL));
randnbr = (unsigned int)(42000000.0 * rand() / (RAND_MAX + 1.0));
snprintf(cam_list[0]->stream.digest_rand
,sizeof(cam_list[0]->stream.digest_rand),"%d",randnbr);
while (cam_list[mhdst.indxthrd] != NULL){
cam_list[mhdst.indxthrd]->stream.daemon = NULL;
if (cam_list[mhdst.indxthrd]->conf->stream_port != 0 ){
if (mhdst.indxthrd == 0){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Starting all camera streams on port %d")
,cam_list[mhdst.indxthrd]->conf->stream_port);
} else {
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Starting camera %d stream on port %d")
,cam_list[mhdst.indxthrd]->camera_id
,cam_list[mhdst.indxthrd]->conf->stream_port);
}
mhdst.mhd_ops=(struct MHD_OptionItem*)mymalloc(sizeof(struct MHD_OptionItem)*WEBUI_MHD_OPTS);
webu_mhd_features(&mhdst);
webu_mhd_opts(&mhdst);
webu_mhd_flags(&mhdst);
if (mhdst.indxthrd == 0){
cam_list[mhdst.indxthrd]->stream.daemon = MHD_start_daemon (mhdst.mhd_flags
,cam_list[mhdst.indxthrd]->conf->stream_port
,NULL, NULL
,&webu_answer_strm, cam_list
,MHD_OPTION_ARRAY, mhdst.mhd_ops
,MHD_OPTION_END);
} else {
cam_list[mhdst.indxthrd]->stream.daemon = MHD_start_daemon (mhdst.mhd_flags
,cam_list[mhdst.indxthrd]->conf->stream_port
,NULL, NULL
,&webu_answer_strm, cam_list[mhdst.indxthrd]
,MHD_OPTION_ARRAY, mhdst.mhd_ops
,MHD_OPTION_END);
}
free(mhdst.mhd_ops);
if (cam_list[mhdst.indxthrd]->stream.daemon == NULL){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Unable to start stream for camera %d")
,cam_list[mhdst.indxthrd]->camera_id);
} else {
webu_strm_ntc(cam_list,mhdst.indxthrd);
}
}
mhdst.indxthrd++;
}
if (mhdst.tls_cert != NULL) free(mhdst.tls_cert);
if (mhdst.tls_key != NULL) free(mhdst.tls_key);
return;
}
static void webu_init_ports(struct ctx_cam **cam_list){
/* Perform check for duplicate ports being specified. The config loading will
* duplicate ports from the motion.conf file to all the cameras so we do not
* log these duplicates to the user and instead just silently set them to zero
*/
int indx, indx2;
if (cam_list[0]->conf->webcontrol_port != 0){
indx = 0;
while (cam_list[indx] != NULL){
if ((cam_list[0]->conf->webcontrol_port == cam_list[indx]->conf->webcontrol_port) && (indx > 0)){
cam_list[indx]->conf->webcontrol_port = 0;
}
if (cam_list[0]->conf->webcontrol_port == cam_list[indx]->conf->stream_port){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Duplicate port requested %d")
,cam_list[indx]->conf->stream_port);
cam_list[indx]->conf->stream_port = 0;
}
indx++;
}
}
/* Now check on the stream ports */
indx = 0;
while (cam_list[indx] != NULL){
if (cam_list[indx]->conf->stream_port != 0){
indx2 = indx + 1;
while (cam_list[indx2] != NULL){
if (cam_list[indx]->conf->stream_port == cam_list[indx2]->conf->stream_port){
if (indx != 0){
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
,_("Duplicate port requested %d")
,cam_list[indx2]->conf->stream_port);
}
cam_list[indx2]->conf->stream_port = 0;
}
indx2++;
}
}
indx++;
}
}
void webu_deinit(struct ctx_motapp *motapp) {
/* This function is called from the main Motion loop to shutdown the
* various MHD connections
*/
int indxthrd;
if (motapp->webcontrol_daemon != NULL){
motapp->webcontrol_finish = TRUE;
MHD_stop_daemon (motapp->webcontrol_daemon);
}
indxthrd = 0;
while (motapp->cam_list[indxthrd] != NULL){
if (motapp->cam_list[indxthrd]->stream.daemon != NULL){
MHD_stop_daemon (motapp->cam_list[indxthrd]->stream.daemon);
}
motapp->cam_list[indxthrd]->stream.daemon = NULL;
indxthrd++;
}
}
void webu_init(struct ctx_motapp *motapp) {
/* This function is called from the main motion thread to start up the
* webcontrol and streams. We need to block some signals otherwise MHD
* will not function correctly.
*/
struct sigaction act;
int indxthrd;
/* set signal handlers TO IGNORE */
memset(&act, 0, sizeof(act));
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, NULL);
sigaction(SIGCHLD, &act, NULL);
motapp->webcontrol_daemon = NULL;
motapp->webcontrol_finish = FALSE;
indxthrd = 0;
while (motapp->cam_list[indxthrd] != NULL){
motapp->cam_list[indxthrd]->stream.daemon = NULL;
motapp->cam_list[indxthrd]->stream.finish = FALSE;
indxthrd++;
}
webu_init_ports(motapp->cam_list);
webu_init_strm(motapp->cam_list);
webu_init_ctrl(motapp);
return;
}