/** * @short Tryes to handle the petition with that handler. * @memberof onion_handler_t * * It needs the handler to handle, the request and the response. * * It checks this parser, and siblings. * * @returns If can not, returns OCS_NOT_PROCESSED (0), else the onion_connection_status. (normally OCS_PROCESSED) */ onion_connection_status onion_handler_handle(onion_handler *handler, onion_request *request, onion_response *response){ onion_connection_status res; while (handler){ if (handler->handler){ #ifdef __DEBUG0__ char **bs=backtrace_symbols((void * const *)&handler->handler, 1); ONION_DEBUG0("Calling handler: %s",bs[0]); /* backtrace_symbols is explicitly documented to malloc. We need to call the system free routine, not our onion_low_free ! */ onion_low_free(bs); /* Can't be onion_low_free.... */ #endif res=handler->handler(handler->priv_data, request, response); ONION_DEBUG0("Result: %d",res); if (res){ // write pending data. if (!(response->flags&OR_HEADER_SENT) && response->buffer_pos<sizeof(response->buffer)) onion_response_set_length(response, response->buffer_pos); onion_response_flush(response); if (res==OCS_WEBSOCKET){ if (request->websocket) return onion_websocket_call(request->websocket); else{ ONION_ERROR("Handler did set the OCS_WEBSOCKET, but did not initialize the websocket on this request."); return OCS_INTERNAL_ERROR; } } return res; } } handler=handler->next; } return OCS_NOT_PROCESSED; }
static onion_dict *onion_sessions_mem_get(onion_sessions *sessions, const char *session_id){ ONION_DEBUG0("Accessing session '%s'",session_id); onion_dict *sess=onion_dict_get_dict(sessions->data, session_id); if (!sess){ ONION_DEBUG0("Unknown session '%s'.", session_id); return NULL; } return onion_dict_dup(sess); }
/** * @short Writes all buffered output waiting for sending. * @ingroup response * * If header has not been sent yet (delayed), it uses a temporary buffer to send it now. This * way header can use the buffer_size information to send the proper content-length, even when it * wasnt properly set by programmer. Whith this information its possib to keep alive the connection * on more cases. */ int onion_response_flush(onion_response * res) { res->sent_bytes += res->buffer_pos; res->sent_bytes_total += res->buffer_pos; if (res->buffer_pos == 0) // Not used. return 0; if (!(res->flags & OR_HEADER_SENT)) { // Automatic header write ONION_DEBUG0 ("Doing fast header hack: store current buffer, send current headers. Resend buffer."); char tmpb[sizeof(res->buffer)]; int tmpp = res->buffer_pos; memcpy(tmpb, res->buffer, res->buffer_pos); res->buffer_pos = 0; onion_response_write_headers(res); onion_response_write(res, tmpb, tmpp); return 0; } if (res->flags & OR_SKIP_CONTENT) // HEAD request return 0; ONION_DEBUG0("Flush %d bytes", res->buffer_pos); onion_request *req = res->request; ssize_t(*write) (onion_request *, const char *data, size_t len); write = req->connection.listen_point->write; ssize_t w; off_t pos = 0; //ONION_DEBUG0("Write %d bytes",res->buffer_pos); if (res->flags & OR_CHUNKED) { char tmp[16]; snprintf(tmp, sizeof(tmp), "%X\r\n", (unsigned int)res->buffer_pos); if ((w = write(req, tmp, strlen(tmp))) <= 0) { ONION_WARNING("Error writing chunk encoding length. Aborting write."); return OCS_CLOSE_CONNECTION; } ONION_DEBUG0("Write %d-%d bytes", res->buffer_pos, w); } while ((w = write(req, &res->buffer[pos], res->buffer_pos)) != res->buffer_pos) { if (w <= 0 || res->buffer_pos < 0) { ONION_ERROR("Error writing %d bytes. Maybe closed connection. Code %d. ", res->buffer_pos, w); perror(""); res->buffer_pos = 0; return OCS_CLOSE_CONNECTION; } pos += w; ONION_DEBUG0("Write %d-%d bytes", res->buffer_pos, w); res->buffer_pos -= w; } if (res->flags & OR_CHUNKED) { write(req, "\r\n", 2); } res->buffer_pos = 0; return 0; }
static onion_connection_status parse_POST_multipart_content_type(onion_request *req, onion_buffer *data){ onion_token *token=req->parser_data; int res=token_read_until(token, data,';'); if (res<=1000) return res; //ONION_DEBUG("Got content type %s",token->str); onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra; const char *name; name=strstr(token->str, "filename="); if (name){ int l=strlen(token->str)-9; if (l>multipart->post_total_size){ ONION_ERROR("Post buffer exhausted. content-Length wrong passed."); return OCS_INTERNAL_ERROR; } multipart->filename=multipart->data; memcpy(multipart->filename, name+9, l); multipart->filename[l]=0; multipart->data=multipart->data+l+1; if (*multipart->filename=='"' && multipart->filename[l-2]=='"'){ multipart->filename[l-2]='\0'; multipart->filename++; } ONION_DEBUG0("Set filename '%s'",multipart->filename); } else{ name=strstr(token->str, "name="); if (name){ int l=strlen(token->str)-5; if (l>multipart->post_total_size){ ONION_ERROR("Post buffer exhausted. Content-Length had wrong size."); return OCS_INTERNAL_ERROR; } multipart->name=multipart->data; memcpy(multipart->name, name+5, l); multipart->name[l]=0; if (*multipart->name=='"' && multipart->name[l-2]=='"'){ l-=2; multipart->name[l]='\0'; multipart->name++; } multipart->data=multipart->name+l+1; ONION_DEBUG0("Set field name '%s'",multipart->name); } } if (res==STRING_NEW_LINE){ req->parser=parse_POST_multipart_headers_key; return OCS_NEED_MORE_DATA; } return OCS_NEED_MORE_DATA; }
static onion_dict* onion_sessions_redis_get(onion_sessions* sessions, const char *session_id) { onion_session_redis *p = sessions->data; ONION_DEBUG0("Load session %s", session_id); onion_dict *ret = NULL; #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); #endif // When commands are sent via redisCommand, they are interpolated by the library // so it will avoid any type of command injection. No need to worry about sending // the session_id directly to redis. redisReply* reply = redisCommand(p->context, "HEXISTS SESSIONS %b", session_id, strlen(session_id)); if(reply->type != REDIS_REPLY_INTEGER) { ONION_ERROR("Error getting session_id"); freeReplyObject(reply); goto exit; } else { if(reply->integer == 1) { freeReplyObject(reply); reply = redisCommand(p->context, "HGET SESSIONS %b", session_id, strlen(session_id)); if(reply->type != REDIS_REPLY_STRING) { ONION_ERROR("Error loading session."); freeReplyObject(reply); goto exit; } else { ret = onion_dict_from_json(reply->str); freeReplyObject(reply); goto exit; } } else { ONION_DEBUG0("No session found. Returning NULL"); freeReplyObject(reply); goto exit; } } exit: #ifdef HAVE_PTHREADS pthread_mutex_unlock(&p->mutex); #endif return ret; }
/** * @short Handles a propfind * * @param path the shared path. */ onion_connection_status onion_webdav_propfind(const char *filename, onion_webdav *wd, onion_request* req, onion_response* res){ // Prepare the basepath, necesary for props. char *basepath=NULL; int pathlen=0; const char *current_path=onion_request_get_path(req); const char *fullpath=onion_request_get_fullpath(req); pathlen=(current_path-fullpath); basepath=alloca(pathlen+1); memcpy(basepath, fullpath, pathlen+1); ONION_DEBUG0("Pathbase initial <%s> %d", basepath, pathlen); while(basepath[pathlen]=='/' && pathlen>0) pathlen--; basepath[pathlen+1]=0; ONION_DEBUG0("PROPFIND; pathbase %s", basepath); int depth; { const char *depths=onion_request_get_header(req, "Depth"); if (!depths){ ONION_ERROR("Missing Depth header on webdav request"); return OCS_INTERNAL_ERROR; } if (strcmp(depths,"infinity")==0){ ONION_ERROR("Infinity depth not supported yet."); return OCS_INTERNAL_ERROR; } depth=atoi(depths); } int props=onion_webdav_parse_propfind(onion_request_get_data(req)); ONION_DEBUG("Asking for props %08X, depth %d", props, depth); onion_block *block=onion_webdav_write_propfind(basepath, filename, onion_request_get_path(req), depth, props); if (!block) // No block, resource does not exist return onion_shortcut_response("Not found", HTTP_NOT_FOUND, req, res); ONION_DEBUG0("Printing block %s", onion_block_data(block)); onion_response_set_header(res, "Content-Type", "text/xml; charset=\"utf-8\""); onion_response_set_length(res, onion_block_size(block)); onion_response_set_code(res, HTTP_MULTI_STATUS); onion_response_write_headers(res); onion_response_flush(res); onion_response_write(res, onion_block_data(block), onion_block_size(block)); onion_block_free(block); return OCS_PROCESSED; }
/** * @short Prepares the PUT * * It saves the data to a temporal file, which name is stored at data. */ static onion_connection_status prepare_PUT(onion_request *req){ onion_token *token=req->parser_data; const char *content_size=onion_dict_get(req->headers, "Content-Length"); if (!content_size){ ONION_ERROR("I need the Content-Length header to get data"); return OCS_INTERNAL_ERROR; } size_t cl=atol(content_size); if (cl>req->connection.listen_point->server->max_file_size){ ONION_ERROR("Trying to PUT a file bigger than allowed size"); return OCS_INTERNAL_ERROR; } req->data=onion_block_new(); char filename[]="/tmp/onion-XXXXXX"; int fd=mkstemp(filename); if (fd<0) ONION_ERROR("Could not create temporary file at %s.", filename); onion_block_add_str(req->data, filename); ONION_DEBUG0("Creating PUT file %s (%d bytes long)", filename, token->extra_size); if (!req->FILES){ req->FILES=onion_dict_new(); } { const char *filename=onion_block_data(req->data); onion_dict_add(req->FILES,"filename", filename, 0); } if (cl==0){ ONION_DEBUG0("Created 0 length file"); close(fd); return OCS_REQUEST_READY; } int *pfd=onion_low_scalar_malloc(sizeof(fd)); *pfd=fd; assert(token->extra==NULL); token->extra=(char*)pfd; token->extra_size=cl; token->pos=0; req->parser=parse_PUT; return OCS_NEED_MORE_DATA; }
/** * @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; }
/** * @short Parses the query part to a given dictionary. * * The data is overwriten as necessary. It is NOT dupped, so if you free this char *p, please free the tree too. */ static void onion_request_parse_query_to_dict(onion_dict *dict, char *p){ ONION_DEBUG0("Query to dict %s",p); char *key=NULL, *value=NULL; int state=0; // 0 key, 1 value key=p; while(*p){ if (state==0){ if (*p=='='){ *p='\0'; value=p+1; state=1; } else if (*p=='&'){ *p='\0'; onion_unquote_inplace(key); ONION_DEBUG0("Adding key %s",key); onion_dict_add(dict, key, "", 0); key=p+1; state=0; } } else{ if (*p=='&'){ *p='\0'; onion_unquote_inplace(key); onion_unquote_inplace(value); ONION_DEBUG0("Adding key %s=%-16s",key,value); onion_dict_add(dict, key, value, 0); key=p+1; state=0; } } p++; } if (state==0){ if (key[0]!='\0'){ onion_unquote_inplace(key); ONION_DEBUG0("Adding key %s",key); onion_dict_add(dict, key, "", 0); } } else{ onion_unquote_inplace(key); onion_unquote_inplace(value); ONION_DEBUG0("Adding key %s=%-16s",key,value); onion_dict_add(dict, key, value, 0); } }
/** * @short Reads from the data to fulfill content-length data. * * All data is writen a temporal file, which will be removed later. */ static onion_connection_status parse_PUT(onion_request *req, onion_buffer *data){ onion_token *token=req->parser_data; int length=data->size-data->pos; int exit=0; if (length>=token->extra_size-token->pos){ exit=1; length=token->extra_size-token->pos; } //ONION_DEBUG0("Writing %d. %d / %d bytes", length, token->pos+length, token->extra_size); int *fd=(int*)token->extra; ssize_t w=write(*fd, &data->data[data->pos], length); if (w<0){ ONION_ERROR("Could not write all data to temporal file."); return OCS_INTERNAL_ERROR; } data->pos+=length; token->pos+=length; #if __DEBUG__ const char *filename=onion_block_data(req->data); ONION_DEBUG0("Done with PUT. Created %s (%d bytes)", filename, token->pos); #endif if (exit){ close (*fd); free(fd); token->extra=NULL; return onion_request_process(req); } return OCS_NEED_MORE_DATA; }
/** * @short Adds a file descriptor to poll. * @memberof onion_poller_t * @ingroup poller * * When new data is available (read/write/event) the given function * is called with that data. * * Once the slot is added is not safe anymore to set data on it, unless its detached from the epoll. (see EPOLLONESHOT) */ int onion_poller_add(onion_poller *poller, onion_poller_slot *el){ ONION_DEBUG0("Adding fd %d for polling (%d)", el->fd, poller->n); pthread_mutex_lock(&poller->mutex); // I like head to be always the same, so I do some tricks to keep it. This is beacuse at head normally I have the listen fd, which is very accessed if (!poller->head) poller->head=el; else{ el->next=poller->head->next; poller->head->next=el; poller->n++; } pthread_mutex_unlock(&poller->mutex); struct epoll_event ev; memset(&ev, 0, sizeof(ev)); ev.events=el->type; ev.data.ptr=el; if (epoll_ctl(poller->fd, EPOLL_CTL_ADD, el->fd, &ev) < 0){ ONION_ERROR("Error add descriptor to listen to. %s", strerror(errno)); return 1; } onion_poller_timer_check(poller, el->timeout_limit); return 1; }
/// @memberof onion_poller_t void onion_poller_free(onion_poller *p){ ONION_DEBUG("Free onion poller: %d waiting", p->n); p->stop=1; close(p->fd); // Wait until all pollers exit. if (pthread_mutex_trylock(&p->mutex)>0){ ONION_WARNING("When cleaning the poller object, some poller is still active; not freeing memory"); } else{ onion_poller_slot *next=p->head; while (next){ onion_poller_slot *tnext=next->next; if (next->shutdown) next->shutdown(next->shutdown_data); next=tnext; } pthread_mutex_unlock(&p->mutex); if (p->eventfd>=0) close(p->eventfd); if (p->timerfd>=0) close(p->timerfd); onion_poller_static_deinit(); onion_low_free(p); } ONION_DEBUG0("Done"); }
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 0 if ((req->flags&OR_METHODS)==OR_POST) return prepare_POST(req); #endif if ((req->flags&OR_METHODS)==OR_PUT) return prepare_PUT(req); if (onion_request_get_header(req, "Content-Length")){ // Soem length, not POST, get data. int n=atoi(onion_request_get_header(req, "Content-Length")); if (n>0) return prepare_CONTENT_LENGTH(req); } return onion_request_process(req); } token->extra=strdup(token->str); req->parser=parse_headers_VALUE; return parse_headers_VALUE(req, data); }
/** * @short Deletes a request and all its data * @memberof onion_request_t */ void onion_request_free(onion_request *req){ ONION_DEBUG0("Free request %p", req); onion_dict_free(req->headers); if (req->parser_data){ onion_request_parser_data_free(req->parser_data); req->parser_data=NULL; } if (req->fullpath) free(req->fullpath); if (req->GET) onion_dict_free(req->GET); if (req->POST) onion_dict_free(req->POST); if (req->FILES){ onion_dict_preorder(req->FILES, unlink_files, NULL); onion_dict_free(req->FILES); } if (req->parser_data) free(req->parser_data); if (req->client_info) free(req->client_info); if (req->session_id) free(req->session_id); if (req->session) onion_dict_free(req->session); // Not really remove, just dereference if (req->data) onion_block_free(req->data); free(req); }
/** * @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 *v=onion_dict_get(req->headers, "Cookie"); ONION_DEBUG("Session ID, maybe from %s",v); char *r=NULL; onion_dict *session; do{ // Check all possible sessions if (r) free(r); if (!v) return; v=strstr(v,"sessionid="); if (!v) // exit point, no session found. return; v+=10; r=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->server->sessions, r); }while(!session); req->session_id=r; req->session=session; ONION_DEBUG("Session ID, from cookie, is %s",req->session_id); }
/** * @short Cleans a request object to reuse it. * @memberof onion_request_t */ void onion_request_clean(onion_request* req){ ONION_DEBUG0("Clean request %p", req); onion_dict_free(req->headers); req->headers=onion_dict_new(); if (req->parser_data){ onion_request_parser_data_free(req->parser_data); req->parser_data=NULL; } req->parser=NULL; req->flags&=0x0F00; // I keep server flags. if (req->fullpath){ free(req->fullpath); req->path=req->fullpath=NULL; } if (req->GET){ onion_dict_free(req->GET); req->GET=NULL; } if (req->POST){ onion_dict_free(req->POST); req->POST=NULL; } if (req->FILES){ onion_dict_preorder(req->FILES, unlink_files, NULL); onion_dict_free(req->FILES); req->FILES=NULL; } if (req->data){ onion_block_free(req->data); req->data=NULL; } }
/** * @short Writes all the header to the given response * @memberof onion_response_t * @ingroup response * * It writes the headers and depending on the method, return OR_SKIP_CONTENT. this is set when in head mode. Handlers * should react to this return by not trying to write more, but if they try this object will just skip those writtings. * * Explicit calling to this function is not necessary, as as soon as the user calls any write function this will * be performed. * * As soon as the headers are written, any modification on them will be just ignored. * * @returns 0 if should procced to normal data write, or OR_SKIP_CONTENT if should not write content. */ int onion_response_write_headers(onion_response * res) { if (!res->request) { ONION_ERROR ("Bad formed response. Need a request at creation. Will not write headers."); return -1; } res->flags |= OR_HEADER_SENT; // I Set at the begining so I can do normal writing. res->request->flags |= OR_HEADER_SENT; char chunked = 0; if (res->request->flags & OR_HTTP11) { onion_response_printf(res, "HTTP/1.1 %d %s\r\n", res->code, onion_response_code_description(res->code)); //ONION_DEBUG("Response header: HTTP/1.1 %d %s\n",res->code, onion_response_code_description(res->code)); if (!(res->flags & OR_LENGTH_SET) && onion_request_keep_alive(res->request)) { onion_response_write(res, CONNECTION_CHUNK_ENCODING, sizeof(CONNECTION_CHUNK_ENCODING) - 1); chunked = 1; } } else { onion_response_printf(res, "HTTP/1.0 %d %s\r\n", res->code, onion_response_code_description(res->code)); //ONION_DEBUG("Response header: HTTP/1.0 %d %s\n",res->code, onion_response_code_description(res->code)); if (res->flags & OR_LENGTH_SET) // On HTTP/1.0, i need to state it. On 1.1 it is default. onion_response_write(res, CONNECTION_KEEP_ALIVE, sizeof(CONNECTION_KEEP_ALIVE) - 1); } if (!(res->flags & OR_LENGTH_SET) && !chunked && !(res->flags & OR_CONNECTION_UPGRADE)) onion_response_write(res, CONNECTION_CLOSE, sizeof(CONNECTION_CLOSE) - 1); if (res->flags & OR_CONNECTION_UPGRADE) onion_response_write(res, CONNECTION_UPGRADE, sizeof(CONNECTION_UPGRADE) - 1); onion_dict_preorder(res->headers, write_header, res); if (res->request->session_id && (onion_dict_count(res->request->session) > 0)) // I have session with something, tell user onion_response_printf(res, "Set-Cookie: sessionid=%s; httponly; Path=/\n", res->request->session_id); onion_response_write(res, "\r\n", 2); ONION_DEBUG0("Headers written"); res->sent_bytes = -res->buffer_pos; // the header size is not counted here. It will add again so start negative. if ((res->request->flags & OR_METHODS) == OR_HEAD) { onion_response_flush(res); res->flags |= OR_SKIP_CONTENT; return OR_SKIP_CONTENT; } if (chunked) { onion_response_flush(res); res->flags |= OR_CHUNKED; } 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); }
/** * @short Creates the onion structure to fill with the server data, and later do the onion_listen() * @memberof onion_t * * Creates an onion structure that can be used to set the server, port, SSL and similar parameters. It works over * the onion structure, which is the main structure to control the listening of new connections throught TCP/IP. * * A normal usage would be like this: * * @code * * onion *o=onion_new(O_THREADED); * onion_set_root_handler(o, onion_handler_directory(".")); * onion_listen(o); * * @endcode * * @param flags Or'ed flags to use at the listening daemon. Normally one of O_ONE, O_ONE_LOOP or O_THREADED. * * @returns The onion structure. * * @see onion_mode_e onion_t */ onion *onion_new(int flags){ ONION_DEBUG0("Some internal sizes: onion size: %d, request size %d, response size %d",sizeof(onion),sizeof(onion_request),sizeof(onion_response)); if(SOCK_CLOEXEC == 0){ ONION_WARNING("There is no support for SOCK_CLOEXEC compiled in libonion. This may be a SECURITY PROBLEM as connections may leak into executed programs."); } onion *o=calloc(1,sizeof(onion)); o->flags=(flags&0x0FF)|O_SSL_AVAILABLE; o->timeout=5000; // 5 seconds of timeout, default. o->poller=onion_poller_new(15); if (!o->poller){ free(o); return NULL; } o->sessions=onion_sessions_new(); o->internal_error_handler=onion_handler_new((onion_handler_handler)onion_default_error, NULL, NULL); o->max_post_size=1024*1024; // 1MB o->max_file_size=1024*1024*1024; // 1GB #ifdef HAVE_PTHREADS o->flags|=O_THREADS_AVALIABLE; o->nthreads=8; if (o->flags&O_THREADED) o->flags|=O_THREADS_ENABLED; #endif return o; }
/** * @short Creates the onion structure to fill with the server data, and later do the onion_listen() * @memberof onion_t * * Creates an onion structure that can be used to set the server, port, SSL and similar parameters. It works over * the onion structure, which is the main structure to control the listening of new connections throught TCP/IP. * * A normal usage would be like this: * * @code * * onion *o=onion_new(O_THREADED); * onion_set_root_handler(o, onion_handler_directory(".")); * onion_listen(o); * * @endcode * * @param flags Or'ed flags to use at the listening daemon. Normally one of O_ONE, O_ONE_LOOP or O_THREADED. * * @returns The onion structure. * * @see onion_mode_e onion_t */ onion *onion_new(int flags){ ONION_DEBUG0("Some internal sizes: onion size: %d, request size %d, response size %d",sizeof(onion),sizeof(onion_request),sizeof(onion_response)); if(SOCK_CLOEXEC == 0){ ONION_WARNING("There is no support for SOCK_CLOEXEC compiled in libonion. This may be a SECURITY PROBLEM as connections may leak into executed programs."); } onion *o=malloc(sizeof(onion)); o->flags=flags&0x0FF; o->listenfd=0; o->server=onion_server_new(); o->timeout=5000; // 5 seconds of timeout, default. o->port=strdup("8080"); o->hostname=strdup("::"); o->username=NULL; #ifdef HAVE_GNUTLS o->flags|=O_SSL_AVAILABLE; #endif #ifdef HAVE_PTHREADS o->flags|=O_THREADS_AVALIABLE; o->max_threads=15; if (o->flags&O_THREADED){ o->flags|=O_THREADS_ENABLED; sem_init(&o->thread_count, 0, o->max_threads); pthread_mutex_init(&o->mutex, NULL); } #endif o->poller=NULL; return o; }
/** * @short Creates a request object * @memberof onion_request_t * * @param op Listen point this request is listening to, to be able to read and write data */ onion_request *onion_request_new(onion_listen_point *op) { onion_request *req; req=onion_low_calloc(1, sizeof(onion_request)); req->connection.listen_point=op; req->connection.fd=-1; //req->connection=con; req->headers=onion_dict_new(); onion_dict_set_flags(req->headers, OD_ICASE); ONION_DEBUG0("Create request %p", req); if (op) { if (op->request_init) { if (op->request_init(req)<0) { ONION_DEBUG("Invalid request, closing"); onion_request_free(req); return NULL; } } else onion_listen_point_request_init_from_socket(req); } return req; }
/** * @short Internal processor of just one request. * @memberof onion_t * * It can be used on one processing, on threaded, on one_loop... * * It reads all the input passes it to the request, until onion_request_writes returns that the * connection must be closed. This function do no close the connection, but the caller must * close it when it returns. * * It also do all the SSL startup when appropiate. * * @param o Onion struct. * @param clientfd is the POSIX file descriptor of the connection. */ static void onion_process_request(onion *o, int clientfd, struct sockaddr_storage *client_addr, socklen_t client_len){ ONION_DEBUG0("Processing request"); onion_request *req=onion_connection_start(o, clientfd, client_addr, client_len); if (!req){ close(clientfd); return; } onion_connection_status cs=OCS_KEEP_ALIVE; struct pollfd pfd; pfd.events=POLLIN; pfd.fd=clientfd; while ((cs>=0) && (poll(&pfd,1, o->timeout)>0)){ cs=onion_connection_read(req); } ONION_DEBUG0("Shutdown connection"); onion_connection_shutdown(req); }
/** * @short Frees the parser data. */ void onion_request_parser_data_free(void *t){ ONION_DEBUG0("Free parser data"); onion_token *token=t; if (token->extra){ onion_low_free(token->extra); token->extra=NULL; } onion_low_free(token); }
/** * @short Default implementation that just closes the connection * @memberof onion_listen_point_t * * @param oc The request */ void onion_listen_point_request_close_socket(onion_request *oc){ int fd=oc->connection.fd; ONION_DEBUG0("Closing connection socket %d",fd); if (fd>=0){ shutdown(fd,SHUT_RDWR); close(fd); oc->connection.fd=-1; } }
/** * @short Cleans a request object to reuse it. * @memberof onion_request_t */ void onion_request_clean(onion_request* req) { ONION_DEBUG0("Clean request %p", req); onion_dict_free(req->headers); req->headers=onion_dict_new(); onion_dict_set_flags(req->headers, OD_ICASE); req->flags&=OR_NO_KEEP_ALIVE; // I keep keep alive. if (req->parser_data) { onion_request_parser_data_free(req->parser_data); req->parser_data=NULL; } if (req->fullpath) { onion_low_free(req->fullpath); req->path=req->fullpath=NULL; } if (req->GET) { onion_dict_free(req->GET); req->GET=NULL; } if (req->POST) { onion_dict_free(req->POST); req->POST=NULL; } if (req->FILES) { onion_dict_preorder(req->FILES, unlink_files, NULL); onion_dict_free(req->FILES); req->FILES=NULL; } if (req->session_id) { if (onion_dict_count(req->session)==0) { onion_request_session_free(req); } else { onion_sessions_save(req->connection.listen_point->server->sessions, req->session_id, req->session); onion_dict_free(req->session); // Not really remove, just dereference req->session=NULL; onion_low_free(req->session_id); req->session_id=NULL; } } if (req->data) { onion_block_free(req->data); req->data=NULL; } if (req->connection.cli_info) { onion_low_free(req->connection.cli_info); req->connection.cli_info=NULL; } if (req->cookies) { onion_dict_free(req->cookies); req->cookies=NULL; } if (req->free_list) { onion_ptr_list_foreach(req->free_list, onion_low_free); onion_ptr_list_free(req->free_list); req->free_list=NULL; } }
/** * Hard parser as I must set into the file as I read, until i found the boundary token (or start), try to parse, and if fail, * write to the file. * * The boundary may be in two or N parts. */ static onion_connection_status parse_POST_multipart_data(onion_request *req, onion_buffer *data){ onion_token *token=req->parser_data; onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra; const char *p=data->data+data->pos; char *d=&multipart->data[token->pos]; for (;data->pos<data->size;data->pos++){ if (multipart->pos==0 && *p=='\n') // \r is optional. multipart->pos=1; if (*p==multipart->boundary[multipart->pos]){ // Check boundary multipart->pos++; if (multipart->pos==multipart->size){ multipart->pos=0; data->pos++; // Not sure why this is needed. FIXME. if (token->pos>0 && *(d-1)=='\n'){ token->pos--; d--; } if (token->pos>0 && *(d-1)=='\r') d--; *d='\0'; ONION_DEBUG0("Adding POST data '%s'",multipart->name); onion_dict_add(req->POST, multipart->name, multipart->data, 0); multipart->data=multipart->data+token->pos+1; token->pos=0; req->parser=parse_POST_multipart_next; return OCS_NEED_MORE_DATA; } } else{ // No boundary if (multipart->pos!=0){// Maybe i was checking before... so I need to copy as much as I wrongly thought I got. if (multipart->post_total_size<=multipart->pos){ ONION_ERROR("No space left for this post."); return OCS_INTERNAL_ERROR; } multipart->post_total_size-=multipart->pos; memcpy(d,p,multipart->pos); d+=multipart->pos; token->pos+=multipart->pos; multipart->pos=0; } if (multipart->post_total_size<=0){ ONION_ERROR("No space left for this post."); return OCS_INTERNAL_ERROR; } multipart->post_total_size--; //ONION_DEBUG0("%d %d",multipart->post_total_size,token->extra_size - (int) (d-token->extra)); *d=*p; d++; token->pos++; } p++; } return OCS_NEED_MORE_DATA; }
/// Just appends to the handler. Must be big enought or segfault.. Just for tests. int buffer_append(buffer *handler, const char *data, unsigned int length){ ONION_DEBUG0("Appending %d bytes",length); int l=length; if (handler->pos+length>handler->size){ l=handler->size-handler->pos; } memcpy(handler->data+handler->pos,data,l); handler->pos+=l; return l; }
void onion_poller_slot_set_type(onion_poller_slot *el, int type){ el->type=EPOLLONESHOT; if (type&O_POLL_READ) el->type|=EPOLLIN; if (type&O_POLL_WRITE) el->type|=EPOLLOUT; if (type&O_POLL_OTHER) el->type|=EPOLLERR|EPOLLHUP|EPOLLPRI; ONION_DEBUG0("Setting type to %d, %d", el->fd, el->type); }
/** * @short Performs the real request: checks if its for me, and then calls the inside level. */ int onion_url_handler(onion_url_data **dd, onion_request *request, onion_response *response){ onion_url_data *next=*dd; regmatch_t match[16]; int i; const char *path=onion_request_get_path(request); while (next){ ONION_DEBUG0("Check %s against %s", onion_request_get_path(request), next->orig); if (next->flags&OUD_STRCMP){ if (strcmp(path, next->str)==0){ ONION_DEBUG0("Ok, simple match."); onion_request_advance_path(request, strlen(next->str)); return onion_handler_handle(next->inside, request, response); } } else if (regexec(&next->regexp, onion_request_get_path(request), 16, match, 0)==0){ //ONION_DEBUG("Ok,match"); onion_dict *reqheader=request->GET; for (i=1;i<16;i++){ regmatch_t *rm=&match[i]; if (rm->rm_so!=-1){ char *tmp=malloc(rm->rm_eo-rm->rm_so); memcpy(tmp, &path[rm->rm_so], rm->rm_eo-rm->rm_so); char tmpn[4]; snprintf(tmpn,sizeof(tmpn),"%d",i); onion_dict_add(reqheader, tmpn, tmp, OD_DUP_KEY|OD_FREE_VALUE); ONION_DEBUG("Add group %d: %s (%d-%d)", i, tmp, rm->rm_so, rm->rm_eo); } else break; } onion_request_advance_path(request, match[0].rm_eo); ONION_DEBUG0("Ok, regexp match."); return onion_handler_handle(next->inside, request, response); } next=next->next; } return 0; }
/// simple answer to the password question, needed by pam static int authPAM_passwd(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr){ ONION_DEBUG0("Num messages %d",num_msg); struct pam_response *r; *resp=r=(struct pam_response*)calloc(num_msg,sizeof(struct pam_response)); if (r==NULL) return PAM_BUF_ERR; int i; for (i=0;i<num_msg;i++){ ONION_DEBUG0("Question %d: %s",i, (*msg)[i].msg); r->resp=strdup((const char*)appdata_ptr); r->resp_retcode=0; r++; } return PAM_SUCCESS; }