/** * @short Sets the port to listen to. * @memberof onion_t * * Default listen point is HTTP at localhost:8080. * * @param server The onion server to act on. * @param port The number of port to listen to, or service name, as string always. */ int onion_add_listen_point(onion* server, const char* hostname, const char* port, onion_listen_point* protocol){ if (protocol==NULL){ ONION_ERROR("Trying to add an invalid entry point. Ignoring."); return -1; } protocol->server=server; if (hostname) protocol->hostname=onion_low_strdup(hostname); if (port) protocol->port=onion_low_strdup(port); if (server->listen_points){ onion_listen_point **p=server->listen_points; int protcount=0; while (*p++) protcount++; server->listen_points= (onion_listen_point**)onion_low_realloc(server->listen_points, (protcount+2)*sizeof(onion_listen_point)); server->listen_points[protcount]=protocol; server->listen_points[protcount+1]=NULL; } else{ server->listen_points=onion_low_malloc(sizeof(onion_listen_point*)*2); server->listen_points[0]=protocol; server->listen_points[1]=NULL; } ONION_DEBUG("add %p listen_point (%p, %p, %p)", protocol, server->listen_points, *server->listen_points, *(server->listen_points+1)); return 0; }
static onion_connection_status parse_headers_KEY(onion_request *req, onion_buffer *data){ onion_token *token=req->parser_data; int res=token_read_KEY(token, data); if (res<=1000) return res; ONION_DEBUG0("Got %d at KEY",res); if ( res == NEW_LINE ){ if ((req->flags&OR_METHODS)==OR_POST){ const char *content_type=onion_request_get_header(req, "Content-Type"); if (content_type && (strstr(content_type,"application/x-www-form-urlencoded") || strstr(content_type, "boundary"))) return prepare_POST(req); } if ((req->flags&OR_METHODS)==OR_PUT) return prepare_PUT(req); if (onion_request_get_header(req, "Content-Length")){ // Some length, not POST, get data. int n=atoi(onion_request_get_header(req, "Content-Length")); if (n>0) return prepare_CONTENT_LENGTH(req); } return OCS_REQUEST_READY; } assert(token->extra==NULL); token->extra=onion_low_strdup(token->str); req->parser=parse_headers_VALUE; return parse_headers_VALUE(req, data); }
/// Sets the data on the node, on the right way. static void onion_dict_set_node_data(onion_dict_node_data *data, const char *key, const void *value, int flags){ //ONION_DEBUG("Set data %02X",flags); if ((flags&OD_DUP_KEY)==OD_DUP_KEY) // not enought with flag, as its a multiple bit flag, with FREE included data->key=onion_low_strdup(key); else data->key=key; if ((flags&OD_DUP_VALUE)==OD_DUP_VALUE){ if (flags&OD_DICT) data->value=onion_dict_hard_dup((onion_dict*)value); else data->value=onion_low_strdup(value); } else data->value=value; data->flags=flags; }
/** * @short Returns the language code of the current request * @memberof onion_request_t * * Returns the language code for the current request, from the header. * If none the returns "C". * * Language code is short code. No localization by the moment. * * @returns The language code for this request or C. Data must be freed. */ const char *onion_request_get_language_code(onion_request *req) { const char *lang=onion_dict_get(req->headers, "Accept-Language"); if (lang) { char *l=onion_low_strdup(lang); char *p=l; while (*p) { // search first part if (*p=='-' || *p==';' || *p==',') { *p=0; break; } p++; } //ONION_DEBUG("Language is %s", l); return l; } return onion_low_strdup("C"); }
/// Sets the hostname on which to listen void onion_set_hostname(onion *server, const char *hostname){ if (server->listen_points){ onion_low_free(server->listen_points[0]->hostname); server->listen_points[0]->hostname=onion_low_strdup(hostname); } else{ onion_add_listen_point(server, hostname, NULL, onion_http_new()); } }
/// Sets the port to listen void onion_set_port(onion *server, const char *port){ if (server->listen_points){ onion_low_free(server->listen_points[0]->port); server->listen_points[0]->port=onion_low_strdup(port); } else{ onion_add_listen_point(server, NULL, port, onion_http_new()); } }
/** * @short Creates a static handler that just writes some static data. * * Path is a regex for the url, as arrived here. */ onion_handler *onion_handler_static(const char *text, int code){ onion_handler_static_data *priv_data=onion_low_malloc(sizeof(onion_handler_static_data)); if (!priv_data) return NULL; priv_data->code=code; priv_data->data=onion_low_strdup(text); onion_handler *ret=onion_handler_new((onion_handler_handler)onion_handler_static_handler, priv_data,(onion_handler_private_data_free) onion_handler_static_delete); return ret; }
/** * @short Returns a string with the client's description. * @memberof onion_request_t * * @return A const char * with the client description */ const char *onion_request_get_client_description(onion_request *req) { if (!req->connection.cli_info && req->connection.cli_len) { char tmp[256]; if (getnameinfo((struct sockaddr *)&req->connection.cli_addr, req->connection.cli_len, tmp, sizeof(tmp)-1, NULL, 0, NI_NUMERICHOST) == 0) { tmp[sizeof(tmp)-1]='\0'; req->connection.cli_info=onion_low_strdup(tmp); } else req->connection.cli_info=NULL; } return req->connection.cli_info; }
/// Set a certificate for use in the connection int onion_set_certificate_va(onion *onion, onion_ssl_certificate_type type, const char *filename, va_list va){ #ifdef HAVE_GNUTLS if (!onion->listen_points){ onion_add_listen_point(onion,NULL,NULL,onion_https_new()); } else{ onion_listen_point *first_listen_point=onion->listen_points[0]; if (first_listen_point->write!=onion_https_write){ if (first_listen_point->write!=onion_http_write){ ONION_ERROR("First listen point is not HTTP not HTTPS. Refusing to promote it to HTTPS. Use proper onion_https_new."); return -1; } ONION_DEBUG("Promoting from HTTP to HTTPS"); char *port=first_listen_point->port ? onion_low_strdup(first_listen_point->port) : NULL; char *hostname=first_listen_point->hostname ? onion_low_strdup(first_listen_point->hostname) : NULL; onion_listen_point_free(first_listen_point); onion_listen_point *https=onion_https_new(); https->server = onion; if (NULL==https){ ONION_ERROR("Could not promote from HTTP to HTTPS. Certificate not set."); } https->port=port; https->hostname=hostname; onion->listen_points[0]=https; first_listen_point=https; } } int r=onion_https_set_certificate_argv(onion->listen_points[0], type, filename, va); return r; #else ONION_ERROR("GNUTLS is not enabled. Recompile onion with GNUTLS support"); return -1; #endif }
/** * @short Gets the dict with the cookies * @memberof onion_request_t * * @param req Request to get the cookies from * * @returns A dict with all the cookies. It might be empty. * * First call it generates the dict. */ onion_dict* onion_request_get_cookies_dict(onion_request* req) { if (req->cookies) return req->cookies; req->cookies=onion_dict_new(); const char *ccookies=onion_request_get_header(req, "Cookie"); if (!ccookies) return req->cookies; char *cookies=onion_low_strdup(ccookies); // I prepare a temporary string, will modify it. char *val=NULL; char *key=NULL; char *p=cookies; int first=1; while(*p) { if (*p!=' ' && !key && !val) { key=p; } else if (*p=='=' && key && !val) { *p=0; val=p+1; } else if (*p==';' && key && val) { *p=0; if (first) { // The first cookie is special as it is the pointer to the reserved area for all the keys and values // for all th eother cookies, to free at dict free. onion_dict_add(req->cookies, cookies, val, OD_FREE_KEY); first=0; } else onion_dict_add(req->cookies, key, val, 0); /// Can use as static data as will be freed at first cookie free ONION_DEBUG0("Add cookie <%s>=<%s> %d", key, val, first); val=NULL; key=NULL; } p++; } if (key && val && val<p) { // A final element, with value. if (first) onion_dict_add(req->cookies, cookies, val, OD_FREE_KEY); else onion_dict_add(req->cookies, key, val, 0); ONION_DEBUG0("Add cookie <%s>=<%s> %d", key, val, first); } return req->cookies; }
static onion_connection_status parse_headers_URL(onion_request *req, onion_buffer *data){ onion_token *token=req->parser_data; int res=token_read_STRING(token, data); if (res<=1000) return res; req->fullpath=onion_low_strdup(token->str); onion_request_parse_query(req); ONION_DEBUG0("URL path is %s", req->fullpath); if (res==STRING_NEW_LINE){ // Old HTTP/0? or lazy user, dont set the HTTP version, straigth new line. req->parser=parse_headers_KEY; return parse_headers_KEY(req, data); } else{ req->parser=parse_headers_VERSION; return parse_headers_VERSION(req, data); } }
/** * @short Gets the sessionid cookie, if any, and sets it to req->session_id. * @memberof onion_request_t */ void onion_request_guess_session_id(onion_request *req) { if (req->session_id) // already known. return; const char *ov=onion_dict_get(req->headers, "Cookie"); const char *v=ov; ONION_DEBUG("Session ID, maybe from %s",v); char *r=NULL; onion_dict *session=NULL; do { // Check all possible sessions if (r) { onion_low_free(r); r=NULL; } if (!v) return; v=strstr(v,"sessionid="); if (!v) // exit point, no session found. return; if (v>ov && isalnum(v[-1])) { ONION_DEBUG("At -1: %c %d (%p %p)",v[-1],isalnum(v[-1]),v,ov); v=strstr(v,";"); } else { v+=10; r=onion_low_strdup(v); // Maybe allocated more memory, not much anyway. char *p=r; while (*p!='\0' && *p!=';') p++; *p='\0'; ONION_DEBUG0("Checking if %s exists in sessions", r); session=onion_sessions_get(req->connection.listen_point->server->sessions, r); } } while(!session); req->session_id=r; req->session=session; ONION_DEBUG("Session ID, from cookie, is %s",req->session_id); }
/** * @short Generates a new response object * @memberof onion_response_t * * This response is generated from a request, and gets from there the writer and writer data. * * Also fills some important data, as server Id, License type, and the default content type. * * Default content type is HTML, as normally this is what is needed. This is nontheless just * the default, and can be changed to any other with a call to: * * onion_response_set_header(res, "Content-Type", my_type); * * The response object must be freed with onion_response_free, which also returns the keep alive * status. * * onion_response objects are passed by onion internally to process the request, and should not be * created by user normally. Nontheless the option exist. * * @returns An onion_response object for that request. */ onion_response *onion_response_new(onion_request *req){ onion_response *res=onion_low_malloc(sizeof(onion_response)); res->request=req; res->headers=onion_dict_new(); res->code=200; // The most normal code, so no need to overwrite it in other codes. res->flags=0; res->sent_bytes_total=res->length=res->sent_bytes=0; res->buffer_pos=0; #ifndef DONT_USE_DATE_HEADER { time_t t; struct tm *tmp; t = time(NULL); // onion_response_last_date_header is set to t later. It should be more or less atomic. // If not no big deal, as we will just use slightly more CPU on those "ephemeral" moments. if (t!=onion_response_last_time){ ONION_DEBUG("Recalculating date header"); char current_datetime[200]; tmp = localtime(&t); if (tmp == NULL) { perror("localtime"); exit(EXIT_FAILURE); } if (strftime(current_datetime, sizeof(current_datetime), "%a, %d %b %Y %H:%M:%S %Z", tmp) == 0) { fprintf(stderr, "strftime returned 0"); exit(EXIT_FAILURE); } // Risky, not using mutex... #ifdef HAVE_PTHREAD pthread_rwlock_wrlock(&onion_response_date_lock); #endif onion_response_last_time=t; if (onion_response_last_date_header) onion_low_free(onion_response_last_date_header); onion_response_last_date_header=onion_low_strdup(current_datetime); #ifdef HAVE_PTHREAD pthread_rwlock_unlock(&onion_response_date_lock); #endif } } #ifdef HAVE_PTHREAD pthread_rwlock_rdlock(&onion_response_date_lock); #endif assert(onion_response_last_date_header); onion_dict_add(res->headers, "Date", onion_response_last_date_header, OD_DUP_VALUE); #ifdef HAVE_PTHREAD pthread_rwlock_unlock(&onion_response_date_lock); #endif #endif // USE_DATE_HEADER // Sorry for the advertisment. onion_dict_add(res->headers, "Server", "libonion v" ONION_VERSION " - coralbits.com", 0); onion_dict_add(res->headers, "Content-Type", "text/html", 0); // Maybe not the best guess, but really useful. //time_t t=time(NULL); //onion_dict_add(res->headers, "Date", asctime(localtime(&t)), OD_DUP_VALUE); return res; }
/** * @short User to which drop priviledges when listening * @memberof onion_t * * Drops the priviledges of current program as soon as it starts listening. * * This is the easiest way to allow low ports and other sensitive info to be used, * but the proper way should be use capabilities and/or SELinux. */ void onion_set_user(onion *server, const char *username){ server->username=onion_low_strdup(username); }