Adrian Rockall

HTTP Cookies

Discussion created by Adrian Rockall on Mar 1, 2016

I thought I would share this to give something back to the community.
I have added support for one time HTTP headers that I use to send cookies to the client and support for reading cookies from received headers.
I am using this for an alternative authentication solution that better fits my needs but it is pretty generic.
The steps are based on the KSDK 1.3 with MQX version of the RTCS. I modified the KSDK / MQX source and then rebuilt the library which is then included in my project.
I have added my name in a comment on the new lines / blocks of code and included small sections of the original surrounding code to help find the locations.

 

1. Add two extra char pointers to the HTTPSRV_SESSION_STRUCT:

/*
* HTTP session structure
*/
typedef struct httpsrv_session_struct
{
    HTTPSRV_SES_FUNC         process_func;       /* Session process function */
    HTTPSRV_SES_STATE        state;              /* HTTP session state */
    volatile uint32_t        valid;              /* Any value different than HTTPSRV_SESSION_VALID means session is invalid */
    volatile uint32_t        sock;               /* Session socket */
    volatile uint32_t        time;               /* Session time. Updated when there is some activity in session. Used for timeout detection. */
    uint32_t                 timeout;            /* Session timeout in ms. timeout_time = time + timeout */
    HTTPSRV_BUFF_STRUCT      buffer;             /* Session internal read/write buffer */
    HTTPSRV_REQ_STRUCT       request;            /* Data read from the request */
    HTTPSRV_RES_STRUCT       response;           /* Response data */
    LWSEM_STRUCT             lock;               /* Session lock */
    volatile _task_id        script_tid;         /* Session script handler */
    HTTPSRV_PLUGIN_STRUCT    *plugin;            /* Plugin to be invoked for session. */
    WS_HANDSHAKE_STRUCT      *ws_handshake;      /* WebSocket hand-shake */
    #if RTCSCFG_ENABLE_SSL
    uint32_t                 ssl_sock;           /* SSL context handle */
    #endif
    uint32_t                 flags;              /* Session flags */
    char*                    onetimeHTTPHdrs;    // Adrian:- Extra headers to be returned to the client for the next transaction
    char*                    recievedCookies;    // Adrian:- Cookies received from client
} HTTPSRV_SESSION_STRUCT;

 

In httpsrv_supp.c at the end of httpsrv_sendhdr add the block between the //Adrian comments:

    /* Handle transfer encoding. */
    if (session->flags & HTTPSRV_FLAG_IS_TRANSCODED)
    {
        httpsrv_print(session, "Transfer-Encoding: chunked\r\n");
    }

    //Adrian
    if (NULL != session->onetimeHTTPHdrs)
    {
        httpsrv_print(session, session->onetimeHTTPHdrs);
        free( session->onetimeHTTPHdrs );
        session->onetimeHTTPHdrs = NULL;
    }
    //End Adrian

    /* End of header */
    httpsrv_print(session, "\r\n");

 

In httpsrv_task.c - httpsrv_ses_alloc add line 27:

        if (session)
        {
            _mem_set_type(session, MEM_TYPE_HTTPSRV_SESSION_STRUCT);
            /* Alloc URI */
            session->request.path = _mem_alloc_zero(server->params.max_uri + 1);
            if (NULL == session->request.path)
            {
                goto ERROR;
            } 
            _mem_set_type(session->request.path, MEM_TYPE_HTTPSRV_URI);
            /* Alloc session buffer */
            session->buffer.data = _mem_alloc_zero(sizeof(char)*HTTPSRV_SES_BUF_SIZE_PRV);
            if (NULL == session->buffer.data)
            {
                goto ERROR;
            }
            #if RTCSCFG_ENABLE_SSL
            if (server->ssl_ctx != 0)
            {
                session->ssl_sock = RTCS_ssl_socket(server->ssl_ctx, sock);
                if (session->ssl_sock == RTCS_ERROR)
                {
                    goto ERROR;
                }
            }
            #endif
            session->onetimeHTTPHdrs = NULL; //Adrian
        }

 

In httpsrv_supp.c - httpsrv_req_hdr add the last else if block:

    else if (strncmp(buffer, "Cookie: ", 8) == 0) //Adrian:- Cookie support
    {
        param_ptr = buffer+8;
        if (NULL != session->recievedCookies)
        {
            _mem_free( session->recievedCookies );
        }
        //Allocate memory for cookies plus two NULL characters at the end
        session->recievedCookies = _mem_alloc_zero( sizeof(char) * strlen( param_ptr ) + (2 * sizeof(char)) );
        if (session->recievedCookies == NULL)
        {
            retval = HTTPSRV_ERR;
            goto EXIT;
        }
        //Copy to the session buffer and replace all ';' with NULL to separate strings (final entry will have 2 NULL), also remove spaces before cookie name.
        char* sessionPtr = session->recievedCookies;
        while (*param_ptr != 0)
        {
            if (*param_ptr == ';')
            {
                //Terminate entry with a NULL in the session buffer
                *sessionPtr = 0;
                param_ptr++;
                while (*param_ptr == ' ')
                {
                    //Skip past space before next cookie name
                    param_ptr++;
                }
            }
            else
            {
                //Copy the character to the session buffer
                *sessionPtr = *param_ptr;
                param_ptr++;
            }
            sessionPtr++;
        }
    }

 

In httpsrv_task.c -  httpsrv_ses_free add the block between //Adrian:

        if (session->ws_handshake)
        {
            _mem_free(session->ws_handshake);
        }
        //Adrian
        if (NULL != session->recievedCookies)
        {
            _mem_free( session->recievedCookies );
        }
        //Adrian end
        _mem_free(session);

 

In httpsrv.h near to the end add:

//Adrian
char* HTTPSRV_GetCookie( uint32_t ses_handle, char* CookieName );
In httpsrv.c near to the end add:
//Retrieve the data for the specified cookie.
//IN:  uint32_t ses_handle - session containing the cookie data
// char* Name - the name of the cookie including the '='
//
//OUT: char* - pointer to the cookie data or NULL if cookie not found
//
char* HTTPSRV_GetCookie( uint32_t ses_handle, char* Name )
{
    HTTPSRV_SESSION_STRUCT* session = (HTTPSRV_SESSION_STRUCT*) ses_handle;
    if (NULL == session->recievedCookies)
    {
        return NULL;
    }
    char* entry = session->recievedCookies;
    uint32_t nameLen = strlen( Name );

    while( (*entry != NULL) && (strncmp( Name, entry, nameLen ) != NULL))
    {
        entry += strlen( entry ) + 1;
    }

    if (*entry == NULL)
    {
        //Not found
        return NULL;
    }

    return (entry + nameLen);
}

 

To add headers via you code use something like (this adds two cookies called uid and sid during my login cgi call-back):

//Add HTTP header so client stores the user name and password cookies
HTTPSRV_SESSION_STRUCT* session = (HTTPSRV_SESSION_STRUCT*)param->ses_handle;
if (NULL == session->onetimeHTTPHdrs)
{
    session->onetimeHTTPHdrs = (char*)malloc( 500 );
}
sprintf( session->onetimeHTTPHdrs, "Set-Cookie: uid=%s; Path=/\r\nSet-Cookie: sid=%s; Path=/; HttpOnly\r\n", gActiveEnetUser.UserID, gActiveEnetUser.SessionID );

 

To get the cookies back use something like during a client request:

//Locate the start of the required cookies in the combined string
char* uid = HTTPSRV_GetCookie( ses_handle, "uid=" );
char* sid = HTTPSRV_GetCookie( ses_handle, "sid=" );

 

I also incorporated a new authentication method that uses the cookies by adding call-back function support to the realm table as follows.

In httpsrv.h add a new type define and add it to the realm table:

// Adrian:- Call-back authentication call-back prototype
typedef int32_t(*HTTPSRV_AUTH_CALLBACK_FN)( uint32_t ses_handle, const char* remote_ip );
/*
** Authentication realm structure
*/
typedef struct httpsrv_auth_realm_struct
{
    const char*                     name;       /* Name of realm. Send to client so user know which login/pass should be used. */
    const char*                     path;       /* Path to file/directory to protect. Relative to root directory */
    const HTTPSRV_AUTH_TYPE         auth_type;  /* Authentication type to use. */
    const HTTPSRV_AUTH_USER_STRUCT* users;      /* Table of allowed users. */
    const HTTPSRV_AUTH_CALLBACK_FN  auth_func; // Adrian:- call-back used to authenticate user
} HTTPSRV_AUTH_REALM_STRUCT;

 

In httpsrv_task.c - httpsrv_req_do add the following block:

    if (session->response.auth_realm != NULL)
    {
        //Adrian:- Implement call-back authentication
        if (NULL != session->response.auth_realm->auth_func)
        {
            char remote_ip[RTCS_IP_ADDR_STR_SIZE];
            struct sockaddr l_address;
            struct sockaddr r_address;
            uint16_t length = sizeof(sockaddr);
            getsockname(session->sock, &l_address, &length);
            getpeername(session->sock, &r_address, &length);
            if (l_address.sa_family == AF_INET)
            {
                inet_ntop(r_address.sa_family, &((struct sockaddr_in*) &r_address)->sin_addr.s_addr, remote_ip, sizeof(remote_ip));
            }
            else if (l_address.sa_family == AF_INET6)
            {
                inet_ntop(r_address.sa_family, ((struct sockaddr_in6*) &r_address)->sin6_addr.s6_addr, remote_ip, sizeof(remote_ip));
            }
            session->response.status_code = session->response.auth_realm->auth_func( (uint32_t)session, remote_ip );
            if (session->response.status_code != HTTPSRV_CODE_OK)
            {
                if (session->request.auth.user_id != NULL)
                {
                    _mem_free(session->request.auth.user_id);
                    session->request.auth.user_id = NULL;
                    session->request.auth.password = NULL;
                }
            goto EXIT;
            }
        }//Adrian End
        else if (!httpsrv_check_auth(session->response.auth_realm, &session->request.auth))

 

In my app I only want one user logged in at a time and I have a database of users that is configured dynamically. So I added the call back functions to the table definition as follows:

int32_t auth_Protect( uint32_t ses_handle, const char* remote_ip );
int32_t auth_Admin( uint32_t ses_handle, const char* remote_ip );
/*
 * Authentication information.
 */
static const HTTPSRV_AUTH_REALM_STRUCT auth_realms[] =
{
    { "Users", "protect", HTTPSRV_AUTH_BASIC, NULL, auth_Protect},
    { "Admin", "admin", HTTPSRV_AUTH_BASIC, NULL, auth_Admin},
    { NULL, NULL, HTTPSRV_AUTH_INVALID, NULL, NULL} /* Array terminator */
};
int32_t auth_Protect( uint32_t ses_handle, const char* remote_ip )
{
    if ((gActiveEnetUser.UserID[ 0 ] == 0) || (strcmp( remote_ip, gActiveEnetUser.ClientIP ) != 0))
    {
        //Either no user logged in or the request is from a different IP source
        return HTTPSRV_CODE_FORBIDDEN;
    }
   
    //Locate the required cookies
    char* uid = HTTPSRV_GetCookie( ses_handle, "uid=" );
    char* sid = HTTPSRV_GetCookie( ses_handle, "sid=" );
    if ((NULL == uid) || (NULL == sid))
    {
        //No user or session id
        return HTTPSRV_CODE_FORBIDDEN;
    }
    if ((*uid == 0) || (*sid == 0))
    {
        //No user id or session id
        return HTTPSRV_CODE_FORBIDDEN;
    }
 
    if ((strcmp( gActiveEnetUser.UserID, uid ) == 0) &&(strcmp( gActiveEnetUser.SessionID, sid ) == 0))
    {
        return HTTPSRV_CODE_OK;
    }
    return HTTPSRV_CODE_FORBIDDEN;
}
int32_t auth_Admin( uint32_t ses_handle, const char* remote_ip )
{
    int32_t response = auth_Protect( ses_handle, remote_ip );
    if (response == HTTPSRV_CODE_OK)
    {
        //Passed basic authentication so check user permissions
        if (gActiveEnetUser.Level < ENET_LEVEL_ADMIN)
        {
            response = HTTPSRV_CODE_FORBIDDEN;
        }
    }

    return response;
}

 

Hopefully I have remembered all the steps and not missed anything.

 

Best regards,
Adrian

Outcomes