/*
 *    webu.c
 *
 *    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.h"
#include "webu.h"
#include "webu_html.h"
#include "webu_text.h"
#include "webu_stream.h"
#include "translate.h"

/* Context to pass the parms to functions to start mhd */
struct mhdstart_ctx {
    struct context          **cnt;
    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 context **cntlst, struct context *cnt, struct webui_ctx *webui) {

    int indx;

    webui->url           = mymalloc(WEBUI_LEN_URLI);
    webui->uri_camid     = mymalloc(WEBUI_LEN_PARM);
    webui->uri_cmd1      = mymalloc(WEBUI_LEN_PARM);
    webui->uri_cmd2      = mymalloc(WEBUI_LEN_PARM);
    webui->uri_parm1     = mymalloc(WEBUI_LEN_PARM);
    webui->uri_value1    = mymalloc(WEBUI_LEN_PARM);
    webui->uri_parm2     = mymalloc(WEBUI_LEN_PARM);
    webui->uri_value2    = mymalloc(WEBUI_LEN_PARM);
    webui->clientip      = mymalloc(WEBUI_LEN_URLI);
    webui->hostname      = mymalloc(WEBUI_LEN_PARM);
    webui->auth_denied   = mymalloc(WEBUI_LEN_RESP);
    webui->auth_opaque   = mymalloc(WEBUI_LEN_PARM);
    webui->auth_realm    = mymalloc(WEBUI_LEN_PARM);
    webui->text_eol      = 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          = mymalloc(3);         /* Two digit lang code plus null terminator */
    webui->lang_full     = 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     = mymalloc(webui->resp_size);      /* The response being constructed */
    webui->cntlst        = cntlst;  /* The list of context's for all cameras */
    webui->cnt           = cnt;     /* The context pointer for a single camera */
    webui->cnct_type     = WEBUI_CNCT_UNKNOWN;

    /* get the number of cameras and threads */
    indx = 0;
    if (webui->cntlst != NULL){
        while (webui->cntlst[++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 cntlist and cnt, 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->cnt != NULL) {
        if (webui->cnt->conf.webcontrol_interface == 1){
            webu_text_badreq(webui);
        } else {
            webu_html_badreq(webui);
        }
    } else if (webui->cntlst != NULL) {
        if (webui->cntlst[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 = mymalloc(webui->resp_size);
        memcpy(temp_resp, webui->resp_page, webui->resp_size);
        free(webui->resp_page);
        webui->resp_page = 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 (cntlist will be populated and this function will set cnt)
     * http://localhost:8081/stream (cntlist will be null, cnt will be populated)
     * http://localhost:8081/   (cntlist will be null, cnt 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 cntlist will be null and the camera context
     * will already be assigned into webui->cnt.  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->cntlst != NULL){
        if (webui->thread_nbr < 0){
            webui->cnt = webui->cntlst[0];
            webui->thread_nbr = 0;
        } else {
            indx = 0;
            while (webui->cntlst[indx] != NULL){
                if (webui->cntlst[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->cnt = webui->cntlst[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 */
            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_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 ((!strcmp(webui->uri_cmd1,"config") ||
         !strcmp(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 ((strcmp(webui->uri_cmd2,"makemovie") == 0) ||
        (strcmp(webui->uri_cmd2,"eventend") == 0)) {
        if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
            while (webui->cntlst[++indx]){
                webui->cntlst[indx]->event_stop = TRUE;
            }
        } else {
            webui->cnt->event_stop = TRUE;
        }

    } else if (strcmp(webui->uri_cmd2,"eventstart") == 0){
        if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
            while (webui->cntlst[++indx]){
                webui->cntlst[indx]->event_user = TRUE;
            }
        } else {
            webui->cnt->event_user = TRUE;
        }

    } else if (!strcmp(webui->uri_cmd2,"snapshot")){
        if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
            while (webui->cntlst[++indx])
            webui->cntlst[indx]->snapshot = 1;
        } else {
            webui->cnt->snapshot = 1;
        }


    } else if (!strcmp(webui->uri_cmd2,"restart")){
        if (webui->thread_nbr == 0) {
            MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Restarting all threads"));
            webui->cntlst[0]->webcontrol_finish = TRUE;
            kill(getpid(),SIGHUP);
        } else {
            MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,
                _("Restarting thread %d"),webui->thread_nbr);
            webui->cnt->restart = TRUE;
            if (webui->cnt->running) {
                webui->cnt->event_stop = TRUE;
                webui->cnt->finish = TRUE;
            }

        }

    } else if (!strcmp(webui->uri_cmd2,"quit")){
        if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
            while (webui->cntlst[++indx]){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,
                    _("Quitting thread %d"),webui->thread_nbr);
                webui->cntlst[indx]->restart = FALSE;
                webui->cntlst[indx]->event_stop = TRUE;
                webui->cntlst[indx]->event_user = TRUE;
                webui->cntlst[indx]->finish = TRUE;
            }
        } else {
            MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO,
                _("Quitting thread %d"),webui->thread_nbr);
            webui->cnt->restart = FALSE;
            webui->cnt->event_stop = TRUE;
            webui->cnt->event_user = TRUE;
            webui->cnt->finish = TRUE;
        }

    } else if (!strcmp(webui->uri_cmd2,"end")){
            MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, _("Motion terminating"));
            while (webui->cntlst[indx]){
                webui->cntlst[indx]->webcontrol_finish = TRUE;
                webui->cntlst[indx]->restart = FALSE;
                webui->cntlst[indx]->event_stop = TRUE;
                webui->cntlst[indx]->event_user = TRUE;
                webui->cntlst[indx]->finish = TRUE;
                indx++;
            }


    } else if (!strcmp(webui->uri_cmd2,"start")){
        if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
            do {
                webui->cntlst[indx]->pause = 0;
            } while (webui->cntlst[++indx]);
        } else {
            webui->cnt->pause = 0;
        }
    } else if (!strcmp(webui->uri_cmd2,"pause")){
        if (webui->thread_nbr == 0 && webui->cam_threads > 1) {
            do {
                webui->cntlst[indx]->pause = 1;
            } while (webui->cntlst[++indx]);
        } else {
            webui->cnt->pause = 1;
        }

    } else if (!strcmp(webui->uri_cmd2,"connection")){
        webu_text_connection(webui);

    } else if (!strcmp(webui->uri_cmd2,"status")){
        webu_text_status(webui);

    } else if ((!strcmp(webui->uri_cmd2,"write")) ||
               (!strcmp(webui->uri_cmd2,"writeyes"))){
        conf_print(webui->cntlst);

    } 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 (dep_config_params[indx].name != NULL) {
        if (strcmp(dep_config_params[indx].name,webui->uri_parm1) == 0){
            snprintf(temp_name, WEBUI_LEN_PARM, "%s", dep_config_params[indx].newname);
            break;
        }
        indx++;
    }
    /* Ignore any request to change an option that is designated above the
     * webcontrol_parms level.
     */
    indx=0;
    while (config_params[indx].param_name != NULL) {
        if (((webui->thread_nbr != 0) && (config_params[indx].main_thread)) ||
            (config_params[indx].webui_level > webui->cntlst[0]->conf.webcontrol_parms) ||
            (config_params[indx].webui_level == WEBUI_LEVEL_NEVER) ) {
            indx++;
            continue;
        }
        if (!strcmp(temp_name, config_params[indx].param_name)) 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_params[indx].param_name != NULL){
        if (strlen(webui->uri_parm1) > 0){
            /* This is legacy assumption on the pointers being sequential
             * We send in the original parm name so it will trigger the depreciated warnings
             * and perform any required transformations from old parm to new parm
             */
            conf_cmdparse(webui->cntlst + webui->thread_nbr
                , webui->uri_parm1, webui->uri_value1);

            /*If we are updating vid parms, set the flag to update the device.*/
            if (!strcmp(config_params[indx].param_name, "vid_control_params") &&
                (webui->cntlst[webui->thread_nbr]->vdev != NULL)){
                webui->cntlst[webui->thread_nbr]->vdev->update_parms = TRUE;
            }

            /* If changing language, do it now */
            if (!strcmp(config_params[indx].param_name, "native_language")){
                nls_enabled = webui->cntlst[webui->thread_nbr]->conf.native_language;
                if (nls_enabled){
                    MOTION_LOG(INF, TYPE_ALL, NO_ERRNO,_("Native Language : on"));
                } else {
                    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 ((!strcmp(webui->uri_cmd1,"config")) &&
        (!strcmp(webui->uri_cmd2,"set"))) {
        retcd = webu_process_config_set(webui);

    } else if ((!strcmp(webui->uri_cmd1,"config")) &&
               (!strcmp(webui->uri_cmd2,"get"))) {
        webu_text_get_query(webui);

    } else if ((!strcmp(webui->uri_cmd1,"config")) &&
               (!strcmp(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 coord cent;
    int retcd;

    if (!strcmp(webui->uri_cmd2, "center")) {
        webui->cntlst[webui->thread_nbr]->moved = track_center(webui->cntlst[webui->thread_nbr], 0, 1, 0, 0);
        retcd = 0;
    } else if (!strcmp(webui->uri_cmd2, "set")) {
        if (!strcmp(webui->uri_parm1, "pan")) {
            cent.width = webui->cntlst[webui->thread_nbr]->imgs.width;
            cent.height = webui->cntlst[webui->thread_nbr]->imgs.height;
            cent.x = atoi(webui->uri_value1);
            cent.y = 0;
            webui->cntlst[webui->thread_nbr]->moved = track_move(webui->cntlst[webui->thread_nbr]
                ,webui->cntlst[webui->thread_nbr]->video_dev
                ,&cent, &webui->cntlst[webui->thread_nbr]->imgs, 1);

            cent.width = webui->cntlst[webui->thread_nbr]->imgs.width;
            cent.height = webui->cntlst[webui->thread_nbr]->imgs.height;
            cent.x = 0;
            cent.y = atoi(webui->uri_value2);
            webui->cntlst[webui->thread_nbr]->moved = track_move(webui->cntlst[webui->thread_nbr]
                ,webui->cntlst[webui->thread_nbr]->video_dev
                ,&cent, &webui->cntlst[webui->thread_nbr]->imgs, 1);
            retcd = 0;
        } else if (!strcasecmp(webui->uri_parm1, "x")) {
            webui->cntlst[webui->thread_nbr]->moved = track_center(webui->cntlst[webui->thread_nbr]
                , webui->cntlst[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->cnt != NULL ){
        if (webui->cnt->conf.webcontrol_ipv6) is_ipv6 = TRUE;
    } else {
        if (webui->cntlst[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->cnt->conf.webcontrol_tls){
            snprintf(webui->hostproto,6,"%s","https");
        } else {
            snprintf(webui->hostproto,6,"%s","http");
        }
    } else {
        if (webui->cnt->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 (strcmp(user, webui->auth_user) != 0){
        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 ((strcmp(user, webui->auth_user) != 0) || (strcmp(pass, webui->auth_pass) != 0)) {
        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 = strlen(webui->cnt->conf.webcontrol_authentication);
        col_pos = strstr(webui->cnt->conf.webcontrol_authentication,":");
        if (col_pos == NULL){
            webui->auth_user = mymalloc(auth_len+1);
            webui->auth_pass = mymalloc(2);
            snprintf(webui->auth_user, auth_len + 1, "%s"
                ,webui->cnt->conf.webcontrol_authentication);
            snprintf(webui->auth_pass, 2, "%s","");
        } else {
            webui->auth_user = mymalloc(auth_len - strlen(col_pos) + 1);
            webui->auth_pass = mymalloc(strlen(col_pos));
            snprintf(webui->auth_user, auth_len - strlen(col_pos) + 1, "%s"
                ,webui->cnt->conf.webcontrol_authentication);
            snprintf(webui->auth_pass, strlen(col_pos), "%s", col_pos + 1);
        }
    } else {
        auth_len = strlen(webui->cnt->conf.stream_authentication);
        col_pos = strstr(webui->cnt->conf.stream_authentication,":");
        if (col_pos == NULL){
            webui->auth_user = mymalloc(auth_len+1);
            webui->auth_pass = mymalloc(2);
            snprintf(webui->auth_user, auth_len + 1, "%s"
                ,webui->cnt->conf.stream_authentication);
            snprintf(webui->auth_pass, 2, "%s","");
        } else {
            webui->auth_user = mymalloc(auth_len - strlen(col_pos) + 1);
            webui->auth_pass = mymalloc(strlen(col_pos));
            snprintf(webui->auth_user, auth_len - strlen(col_pos) + 1, "%s"
                ,webui->cnt->conf.stream_authentication);
            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->cnt->conf.webcontrol_authentication == NULL){
            webui->authenticated = TRUE;
            if (webui->cnt->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->cnt->conf.webcontrol_auth_method == 1){
            return webu_mhd_basic(webui);
        } else if (webui->cnt->conf.webcontrol_auth_method == 2){
            return webu_mhd_digest(webui);
        }

    } else {
        /* Authentication for the streams */
        if (webui->cnt->conf.stream_authentication == NULL){
            webui->authenticated = TRUE;
            if (webui->cnt->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->cnt->conf.stream_auth_method == 1) {
            return webu_mhd_basic(webui);
        } else if (webui->cnt->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->cnt = 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->cnt != NULL){
        if (ctrl){
            if (webui->cnt->conf.webcontrol_cors_header != NULL){
                MHD_add_response_header (response, MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
                    , webui->cnt->conf.webcontrol_cors_header);
            }
            if (webui->cnt->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->cnt->conf.stream_cors_header != NULL){
                MHD_add_response_header (response, MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
                    , webui->cnt->conf.stream_cors_header);
            }
            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 ((strcmp(webui->uri_cmd1,"stream") == 0) ||
        (strcmp(webui->uri_camid,"stream") == 0) ||
        (strlen(webui->uri_camid) == 0)) {
        webui->cnct_type = WEBUI_CNCT_FULL;

    } else if ((strcmp(webui->uri_cmd1,"substream") == 0) ||
        (strcmp(webui->uri_camid,"substream") == 0)){
        webui->cnct_type = WEBUI_CNCT_SUB;

    } else if ((strcmp(webui->uri_cmd1,"motion") == 0) ||
        (strcmp(webui->uri_camid,"motion") == 0)){
        webui->cnct_type = WEBUI_CNCT_MOTION;

    } else if ((strcmp(webui->uri_cmd1,"source") == 0) ||
        (strcmp(webui->uri_camid,"source") == 0)){
        webui->cnct_type = WEBUI_CNCT_SOURCE;

    } else if ((strcmp(webui->uri_cmd1,"current") == 0) ||
        (strcmp(webui->uri_camid,"current") == 0)){
        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 = *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 (strcmp (method, "GET") != 0){
        MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Invalid Method requested: %s"),method);
        return MHD_NO;
    }

    webui->cnct_type = WEBUI_CNCT_CONTROL;

    util_threadname_set("wu", 0,NULL);

    webui->connection = connection;

    /* Throw bad URLS back to user*/
    if ((webui->cnt ==  NULL) || (strlen(webui->url) == 0)){
        webu_badreq(webui);
        retcd = webu_mhd_send(webui, FALSE);
        return retcd;
    }

    if (webui->cnt->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->cntlst[0]->conf.webcontrol_interface == 1) ||
        (webui->cntlst[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 = *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;
    }

    /* Do not answer a request until the motion loop has completed at least once */
    if (webui->cnt->passflag == 0) return MHD_NO;

    if (strcmp (method, "GET") != 0){
        MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Invalid Method requested: %s"),method);
        return MHD_NO;
    }

    util_threadname_set("st", 0,NULL);

    webui->connection = connection;

    /* Throw bad URLS back to user*/
    if ((webui->cnt ==  NULL) || (strlen(webui->url) == 0)){
        webu_badreq(webui);
        retcd = webu_mhd_send(webui, FALSE);
        return retcd;
    }

    if (webui->cnt->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 context **cnt = cls;
    struct webui_ctx *webui;
    int retcd;

    (void)connection;

    /* Set the thread name to connection until we know whether control or stream answers*/
    util_threadname_set("cn", 0,NULL);

    webui = malloc(sizeof(struct webui_ctx));

    webu_context_init(cnt, 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->cntlist) and instead assigns the particular
     * camera context to webui->cnt
     */
    struct context *cnt = cls;
    struct webui_ctx *webui;
    int retcd;

    (void)connection;

    /* Set the thread name to connection until we know whether control or stream answers*/
    util_threadname_set("cn", 0,NULL);

    webui = malloc(sizeof(struct webui_ctx));

    webu_context_init(NULL, cnt, 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 = *con_cls;

    /* Eliminate compiler warnings */
    (void)connection;
    (void)cls;
    (void)toe;

    if (webui->cnct_type == WEBUI_CNCT_FULL ){
        pthread_mutex_lock(&webui->cnt->mutex_stream);
            webui->cnt->stream_norm.cnct_count--;
        pthread_mutex_unlock(&webui->cnt->mutex_stream);

    } else if (webui->cnct_type == WEBUI_CNCT_SUB ){
        pthread_mutex_lock(&webui->cnt->mutex_stream);
            webui->cnt->stream_sub.cnct_count--;
        pthread_mutex_unlock(&webui->cnt->mutex_stream);

    } else if (webui->cnct_type == WEBUI_CNCT_MOTION ){
        pthread_mutex_lock(&webui->cnt->mutex_stream);
            webui->cnt->stream_motion.cnct_count--;
        pthread_mutex_unlock(&webui->cnt->mutex_stream);

    } else if (webui->cnct_type == WEBUI_CNCT_SOURCE ){
        pthread_mutex_lock(&webui->cnt->mutex_stream);
            webui->cnt->stream_source.cnct_count--;
        pthread_mutex_unlock(&webui->cnt->mutex_stream);

    } else if (webui->cnct_type == WEBUI_CNCT_STATIC ){
        pthread_mutex_lock(&webui->cnt->mutex_stream);
            webui->cnt->stream_norm.cnct_count--;
        pthread_mutex_unlock(&webui->cnt->mutex_stream);

    }

    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->cnt[mhdst->indxthrd]->conf.webcontrol_auth_method == 1)){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Basic authentication: disabled"));
                mhdst->cnt[mhdst->indxthrd]->conf.webcontrol_auth_method = 0;
            } else if ((!mhdst->ctrl) && (mhdst->cnt[mhdst->indxthrd]->conf.stream_auth_method == 1)){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Basic authentication: disabled"));
                mhdst->cnt[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->cnt[mhdst->indxthrd]->conf.webcontrol_auth_method == 2)){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Digest authentication: disabled"));
                mhdst->cnt[mhdst->indxthrd]->conf.webcontrol_auth_method = 0;
            } else if ((!mhdst->ctrl) && (mhdst->cnt[mhdst->indxthrd]->conf.stream_auth_method == 2)){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("Digest authentication: disabled"));
                mhdst->cnt[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->cnt[mhdst->indxthrd]->conf.webcontrol_tls)){
            MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("libmicrohttpd libary too old SSL/TLS disabled"));
            mhdst->cnt[mhdst->indxthrd]->conf.webcontrol_tls = 0;
        } else if ((!mhdst->ctrl) && (mhdst->cnt[mhdst->indxthrd]->conf.stream_tls)) {
            MOTION_LOG(INF, TYPE_STREAM, NO_ERRNO ,_("libmicrohttpd libary too old SSL/TLS disabled"));
            mhdst->cnt[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->cnt[mhdst->indxthrd]->conf.webcontrol_tls)){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("SSL/TLS: disabled"));
                mhdst->cnt[mhdst->indxthrd]->conf.webcontrol_tls = 0;
            } else if ((!mhdst->ctrl) && (mhdst->cnt[mhdst->indxthrd]->conf.stream_tls)){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("SSL/TLS: disabled"));
                mhdst->cnt[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 = 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->cnt[0]->conf.webcontrol_tls){
            if ((mhdst->cnt[0]->conf.webcontrol_cert == NULL) || (mhdst->tls_cert == NULL)) {
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("SSL/TLS requested but no cert file provided.  SSL/TLS disabled"));
                mhdst->cnt[0]->conf.webcontrol_tls = 0;
            }
            if ((mhdst->cnt[0]->conf.webcontrol_key == NULL) || (mhdst->tls_key == NULL)) {
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("SSL/TLS requested but no key file provided.  SSL/TLS disabled"));
                mhdst->cnt[0]->conf.webcontrol_tls = 0;
            }
        }
    } else {
        if (mhdst->cnt[mhdst->indxthrd]->conf.stream_tls){
            if ((mhdst->cnt[0]->conf.webcontrol_cert == NULL) || (mhdst->tls_cert == NULL)) {
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("SSL/TLS requested but no cert file provided.  SSL/TLS disabled"));
                mhdst->cnt[mhdst->indxthrd]->conf.stream_tls = 0;
            }
            if ((mhdst->cnt[0]->conf.webcontrol_key == NULL) || (mhdst->tls_key == NULL)) {
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("SSL/TLS requested but no key file provided.  SSL/TLS disabled"));
                mhdst->cnt[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->cnt[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->cnt;
        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->cnt[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->cnt[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->cnt[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->cnt[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->cnt[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->cnt[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->cnt[mhdst->indxthrd]->conf.webcontrol_auth_method == 2)) ||
        ((!mhdst->ctrl) && (mhdst->cnt[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->cnt[mhdst->indxthrd]->webcontrol_digest_rand);
            mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->cnt[mhdst->indxthrd]->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->cnt[mhdst->indxthrd]->webstream_digest_rand);
            mhdst->mhd_ops[mhdst->mhd_opt_nbr].ptr_value = mhdst->cnt[mhdst->indxthrd]->webstream_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->cnt[mhdst->indxthrd]->conf.webcontrol_tls)) ||
        ((!mhdst->ctrl) && (mhdst->cnt[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 | MHD_USE_POLL| MHD_USE_SELECT_INTERNALLY;

    if (mhdst->ipv6) mhdst->mhd_flags = mhdst->mhd_flags | MHD_USE_DUAL_STACK;

    if ((mhdst->ctrl) && (mhdst->cnt[mhdst->indxthrd]->conf.webcontrol_tls)){
        mhdst->mhd_flags = mhdst->mhd_flags | MHD_USE_SSL;
    } else if ((!mhdst->ctrl) && (mhdst->cnt[mhdst->indxthrd]->conf.stream_tls)){
        mhdst->mhd_flags = mhdst->mhd_flags | MHD_USE_SSL;
    }

}

static void webu_start_ctrl(struct context **cnt){
    /* 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(cnt[0]->conf.webcontrol_cert);
    mhdst.tls_key  = webu_mhd_loadfile(cnt[0]->conf.webcontrol_key);
    mhdst.ctrl = TRUE;
    mhdst.indxthrd = 0;
    mhdst.cnt = cnt;
    mhdst.ipv6 = cnt[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(cnt[0]->webcontrol_digest_rand
        ,sizeof(cnt[0]->webcontrol_digest_rand),"%d",randnbr);

    cnt[0]->webcontrol_daemon = NULL;
    if (cnt[0]->conf.webcontrol_port != 0 ){
        MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
            ,_("Starting webcontrol on port %d")
            ,cnt[0]->conf.webcontrol_port);

        mhdst.mhd_ops = malloc(sizeof(struct MHD_OptionItem)*WEBUI_MHD_OPTS);
        webu_mhd_features(&mhdst);
        webu_mhd_opts(&mhdst);
        webu_mhd_flags(&mhdst);

        cnt[0]->webcontrol_daemon = MHD_start_daemon (mhdst.mhd_flags
            ,cnt[0]->conf.webcontrol_port
            ,NULL, NULL
            ,&webu_answer_ctrl, cnt
            ,MHD_OPTION_ARRAY, mhdst.mhd_ops
            ,MHD_OPTION_END);
        free(mhdst.mhd_ops);
        if (cnt[0]->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")
                ,cnt[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_strm_ntc(struct context **cnt, int indxthrd){
    int indx;

    if (indxthrd == 0 ){
        if (cnt[1] != NULL) {
            indx = 1;
            while (cnt[indx] != NULL){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("Started camera %d stream on port/camera_id %d/%d")
                    ,cnt[indx]->camera_id
                    ,cnt[indxthrd]->conf.stream_port
                    ,cnt[indx]->camera_id);
                indx++;
            }
        } else {
            MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                ,_("Started camera %d stream on port %d")
                ,cnt[indxthrd]->camera_id,cnt[indxthrd]->conf.stream_port);
        }
    } else {
        MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
            ,_("Started camera %d stream on port %d")
            ,cnt[indxthrd]->camera_id,cnt[indxthrd]->conf.stream_port);
    }
}

static void webu_start_strm(struct context **cnt){
    /* 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(cnt[0]->conf.webcontrol_cert);
    mhdst.tls_key  = webu_mhd_loadfile(cnt[0]->conf.webcontrol_key);
    mhdst.ctrl = FALSE;
    mhdst.indxthrd = 0;
    mhdst.cnt = cnt;
    mhdst.ipv6 = cnt[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(cnt[0]->webstream_digest_rand
        ,sizeof(cnt[0]->webstream_digest_rand),"%d",randnbr);

    while (cnt[mhdst.indxthrd] != NULL){
        cnt[mhdst.indxthrd]->webstream_daemon = NULL;
        if (cnt[mhdst.indxthrd]->conf.stream_port != 0 ){
            if (mhdst.indxthrd == 0){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("Starting all camera streams on port %d")
                    ,cnt[mhdst.indxthrd]->conf.stream_port);
            } else {
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("Starting camera %d stream on port %d")
                    ,cnt[mhdst.indxthrd]->camera_id
                    ,cnt[mhdst.indxthrd]->conf.stream_port);
            }

            mhdst.mhd_ops= malloc(sizeof(struct MHD_OptionItem)*WEBUI_MHD_OPTS);
            webu_mhd_features(&mhdst);
            webu_mhd_opts(&mhdst);
            webu_mhd_flags(&mhdst);
            if (mhdst.indxthrd == 0){
                cnt[mhdst.indxthrd]->webstream_daemon = MHD_start_daemon (mhdst.mhd_flags
                    ,cnt[mhdst.indxthrd]->conf.stream_port
                    ,NULL, NULL
                    ,&webu_answer_strm, cnt
                    ,MHD_OPTION_ARRAY, mhdst.mhd_ops
                    ,MHD_OPTION_END);
            } else {
                cnt[mhdst.indxthrd]->webstream_daemon = MHD_start_daemon (mhdst.mhd_flags
                    ,cnt[mhdst.indxthrd]->conf.stream_port
                    ,NULL, NULL
                    ,&webu_answer_strm, cnt[mhdst.indxthrd]
                    ,MHD_OPTION_ARRAY, mhdst.mhd_ops
                    ,MHD_OPTION_END);
            }
            free(mhdst.mhd_ops);
            if (cnt[mhdst.indxthrd]->webstream_daemon == NULL){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("Unable to start stream for camera %d")
                    ,cnt[mhdst.indxthrd]->camera_id);
            } else {
                webu_strm_ntc(cnt,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_start_ports(struct context **cnt){
    /* 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 (cnt[0]->conf.webcontrol_port != 0){
        indx = 0;
        while (cnt[indx] != NULL){
            if ((cnt[0]->conf.webcontrol_port == cnt[indx]->conf.webcontrol_port) && (indx > 0)){
                cnt[indx]->conf.webcontrol_port = 0;
            }

            if (cnt[0]->conf.webcontrol_port == cnt[indx]->conf.stream_port){
                MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                    ,_("Duplicate port requested %d")
                    ,cnt[indx]->conf.stream_port);
                cnt[indx]->conf.stream_port = 0;
            }

            indx++;
        }
    }

    /* Now check on the stream ports */
    indx = 0;
    while (cnt[indx] != NULL){
        if (cnt[indx]->conf.stream_port != 0){
            indx2 = indx + 1;
            while (cnt[indx2] != NULL){
                if (cnt[indx]->conf.stream_port == cnt[indx2]->conf.stream_port){
                    if (indx != 0){
                        MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO
                            ,_("Duplicate port requested %d")
                            ,cnt[indx2]->conf.stream_port);
                    }
                    cnt[indx2]->conf.stream_port = 0;
                }
                indx2++;
            }
        }
        indx++;
    }
}

void webu_stop(struct context **cnt) {
    /* This function is called from the main Motion loop to shutdown the
     * various MHD connections
     */
    int indxthrd;

    if (cnt[0]->webcontrol_daemon != NULL){
        cnt[0]->webcontrol_finish = TRUE;
        MHD_stop_daemon (cnt[0]->webcontrol_daemon);
    }


    indxthrd = 0;
    while (cnt[indxthrd] != NULL){
        if (cnt[indxthrd]->webstream_daemon != NULL){
            cnt[indxthrd]->webcontrol_finish = TRUE;
            MHD_stop_daemon (cnt[indxthrd]->webstream_daemon);
        }
        cnt[indxthrd]->webstream_daemon = NULL;
        cnt[indxthrd]->webcontrol_daemon = NULL;
        indxthrd++;
    }
}

void webu_start(struct context **cnt) {
    /* 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);


    indxthrd = 0;
    while (cnt[indxthrd] != NULL){
        cnt[indxthrd]->webstream_daemon = NULL;
        cnt[indxthrd]->webcontrol_daemon = NULL;
        cnt[indxthrd]->webcontrol_finish = FALSE;
        indxthrd++;
    }

    if (cnt[0]->conf.stream_preview_method != 99){
        webu_start_ports(cnt);

        webu_start_strm(cnt);
    }

    webu_start_ctrl(cnt);

    return;

}


