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