/** * @short Creates a redis backend for sessions * @ingroup sessions */ onion_sessions* onion_sessions_redis_new(const char* server_ip, int port) { onion_random_init(); onion_sessions *ret = onion_low_malloc(sizeof(onion_sessions)); ret->data = onion_low_malloc(sizeof(onion_session_redis)); ret->free=onion_sessions_redis_free; ret->get=onion_sessions_redis_get; ret->save=onion_sessions_redis_save; onion_session_redis *p = ret->data; p->context = redisConnect(server_ip, port); if(p->context != NULL && p->context->err) { ONION_ERROR("Can't connect to redis. Error (%s)", p->context->errstr); redisFree(p->context); return NULL; } #ifdef HAVE_PTHREADS pthread_mutex_init(&p->mutex, NULL); #endif return ret; }
/** * @short Creates a sqlite backend for sessions * @ingroup sessions * * @see onion_set_session_backend */ onion_sessions* onion_sessions_sqlite3_new(const char *database_filename) { onion_random_init(); int rc; sqlite3 *db; sqlite3_stmt *save; sqlite3_stmt *get; rc = sqlite3_open(database_filename, &db); if( rc ){ ONION_ERROR("Can't open database: %s", sqlite3_errmsg(db)); sqlite3_close(db); return NULL; } // I blindly try to create tables. If they exist, error, if not, create sqlite3_exec(db, "CREATE TABLE sessions (id TEXT PRIMARY KEY, data TEXT)", 0,0,0); const char *GET_SQL="SELECT data FROM sessions WHERE id=?"; rc=sqlite3_prepare_v2(db, GET_SQL, strlen(GET_SQL), &get, NULL); if( rc != SQLITE_OK ){ ONION_ERROR("Cant prepare statement to get (%d)", rc); sqlite3_close(db); return NULL; } const char *SAVE_SQL="INSERT OR REPLACE INTO sessions (id, data) VALUES (?, ?);"; rc=sqlite3_prepare_v2(db, SAVE_SQL, strlen(SAVE_SQL), &save, NULL); if( rc != SQLITE_OK ){ ONION_ERROR("Cant prepare statement to save (%d)", rc); sqlite3_close(db); return NULL; } onion_sessions *ret=onion_low_malloc(sizeof(onion_sessions)); ret->data=onion_low_malloc(sizeof(onion_session_sqlite3)); ret->free=onion_sessions_sqlite3_free; ret->get=onion_sessions_sqlite3_get; ret->save=onion_sessions_sqlite3_save; onion_session_sqlite3 *p=ret->data; p->db=db; p->save=save; p->get=get; #ifdef HAVE_PTHREADS pthread_mutex_init(&p->mutex, NULL); #endif return ret; }
/** * @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; }
/** * @short Returns a poller object that helps polling on sockets and files * @memberof onion_poller_t * * This poller is implemented through epoll, but other implementations are possible * */ onion_poller *onion_poller_new(int n){ onion_poller *p=onion_low_malloc(sizeof(onion_poller)); p->fd=epoll_create1(EPOLL_CLOEXEC); if (p->fd < 0){ ONION_ERROR("Error creating the poller. %s", strerror(errno)); onion_low_free(p); return NULL; } p->eventfd=eventfd(0,EFD_CLOEXEC | EFD_NONBLOCK); #if EFD_CLOEXEC == 0 fcntl(p->eventfd,F_SETFD,FD_CLOEXEC); #endif p->head=NULL; p->n=0; p->stop=0; #ifdef HAVE_PTHREADS ONION_DEBUG("Init thread stuff for poll. Eventfd at %d", p->eventfd); p->npollers=0; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&p->mutex, &attr); pthread_mutexattr_destroy(&attr); #endif onion_poller_slot *ev=onion_poller_slot_new(p->eventfd,onion_poller_stop_helper,p); onion_poller_add(p,ev); return p; }
/// Allocates a new node data, and sets the data itself. static onion_dict_node *onion_dict_node_new(const char *key, const void *value, int flags){ onion_dict_node *node=onion_low_malloc(sizeof(onion_dict_node)); onion_dict_set_node_data(&node->data, key, value, flags); node->left=NULL; node->right=NULL; node->level=1; return node; }
/** * @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; }
onion_sessions *onion_sessions_mem_new(){ onion_random_init(); onion_sessions *ret=onion_low_malloc(sizeof(onion_sessions)); ret->data=onion_dict_new(); ret->get=onion_sessions_mem_get; ret->save=onion_sessions_mem_save; ret->free=onion_sessions_mem_free; return ret; }
onion_connection_status handler(void *request_list_v, onion_request * req, onion_response * res) { // Some hello message onion_response_printf(res, "Starting poll\n"); onion_response_flush(res); // Add it to the request_list lists. request_list_t *request_list = request_list_v; reqres_t *reqres = onion_low_malloc(sizeof(reqres_t)); reqres->req = req; reqres->res = res; pthread_mutex_lock(&request_list->lock); request_list->reqres = onion_ptr_list_add(request_list->reqres, reqres); pthread_mutex_unlock(&request_list->lock); // Yield thread to the poller, ensures request and response are not freed. return OCS_YIELD; }
/** * @short Prepares the POST */ static onion_connection_status prepare_POST(onion_request *req){ // ok post onion_token *token=req->parser_data; const char *content_type=onion_dict_get(req->headers, "Content-Type"); const char *content_size=onion_dict_get(req->headers, "Content-Length"); if (!content_size){ ONION_ERROR("I need the content size header to support POST data"); return OCS_INTERNAL_ERROR; } size_t cl=atol(content_size); if (cl==0) return OCS_REQUEST_READY; //ONION_DEBUG("Content type %s",content_type); if (!content_type || (strstr(content_type, "application/x-www-form-urlencoded"))){ if (cl>req->connection.listen_point->server->max_post_size){ ONION_ERROR("Asked to send much POST data. Limit %d. Failing.",req->connection.listen_point->server->max_post_size); return OCS_INTERNAL_ERROR; } assert(token->extra==NULL); token->extra=onion_low_scalar_malloc(cl+1); // Cl + \0 token->extra_size=cl; req->free_list=onion_ptr_list_add(req->free_list, token->extra); // Free when the request is freed. req->parser=parse_POST_urlencode; return OCS_NEED_MORE_DATA; } // multipart. const char *mp_token=strstr(content_type, "boundary="); if (!mp_token){ ONION_ERROR("No boundary set at content-type"); return OCS_INTERNAL_ERROR; } mp_token+=9; if (cl>req->connection.listen_point->server->max_post_size) // I hope the missing part is files, else error later. cl=req->connection.listen_point->server->max_post_size; int mp_token_size=strlen(mp_token); token->extra_size=cl; // Max size of the multipart->data onion_multipart_buffer *multipart=onion_low_malloc(token->extra_size+sizeof(onion_multipart_buffer)+mp_token_size+2); assert(token->extra==NULL); token->extra=(char*)multipart; multipart->boundary=(char*)multipart+sizeof(onion_multipart_buffer)+1; multipart->size=mp_token_size+4; multipart->pos=2; // First boundary already have [\r]\n readen multipart->post_total_size=cl; multipart->file_total_size=0; multipart->boundary[0]='\r'; multipart->boundary[1]='\n'; multipart->boundary[2]='-'; multipart->boundary[3]='-'; strcpy(&multipart->boundary[4],mp_token); multipart->data=(char*)multipart+sizeof(onion_multipart_buffer)+multipart->size+1; //ONION_DEBUG("Multipart POST boundary '%s'",multipart->boundary); req->parser=parse_POST_multipart_start; return OCS_NEED_MORE_DATA; }
/** * @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 Performs the listening with the given mode * @memberof onion_t * * This is the main loop for the onion server. * * It initiates the listening on all the selected ports and addresses. * * @returns !=0 if there is any error. It returns actualy errno from the network operations. See socket for more information. */ int onion_listen(onion *o){ #ifdef HAVE_PTHREADS if (!(o->flags&O_DETACHED) && (o->flags&O_DETACH_LISTEN)){ // Must detach and return o->flags|=O_DETACHED; int errcode=onion_low_pthread_create(&o->listen_thread,NULL, onion_listen_start, o); if (errcode!=0) return errcode; return 0; } #endif if (!o->listen_points){ onion_add_listen_point(o,NULL,NULL,onion_http_new()); ONION_DEBUG("Created default HTTP listen port"); } /// Start listening size_t successful_listened_points=0; onion_listen_point **lp=o->listen_points; while (*lp){ int listen_result=onion_listen_point_listen(*lp); if (!listen_result) { successful_listened_points++; } lp++; } if (!successful_listened_points){ ONION_ERROR("There are no available listen points"); return 1; } o->flags|=O_LISTENING; if (o->flags&O_ONE){ onion_listen_point **listen_points=o->listen_points; if (listen_points[1]!=NULL){ ONION_WARNING("Trying to use non-poll and non-thread mode with several listen points. Only the first will be listened"); } onion_listen_point *op=listen_points[0]; do{ onion_request *req=onion_request_new(op); if (!req) continue; ONION_DEBUG("Accepted request %p", req); onion_request_set_no_keep_alive(req); int ret; do{ ret=req->connection.listen_point->read_ready(req); }while(ret>=0); ONION_DEBUG("End of request %p", req); onion_request_free(req); //req->connection.listen_point->close(req); }while(((o->flags&O_ONE_LOOP) == O_ONE_LOOP) && op->listenfd>0); } else{ onion_listen_point **listen_points=o->listen_points; while (*listen_points){ onion_listen_point *p=*listen_points; ONION_DEBUG("Adding listen point fd %d to poller", p->listenfd); onion_poller_slot *slot=onion_poller_slot_new(p->listenfd, (void*)onion_listen_point_accept, p); onion_poller_slot_set_type(slot, O_POLL_ALL); onion_poller_add(o->poller, slot); listen_points++; } #ifdef HAVE_PTHREADS ONION_DEBUG("Start polling / listening %p, %p, %p", o->listen_points, *o->listen_points, *(o->listen_points+1)); if (o->flags&O_THREADED){ o->threads=onion_low_malloc(sizeof(pthread_t)*(o->nthreads-1)); int i; for (i=0;i<o->nthreads-1;i++){ onion_low_pthread_create(&o->threads[i],NULL,onion_poller_poll_start, o->poller); } // Here is where it waits.. but eventually it will exit at onion_listen_stop onion_poller_poll(o->poller); ONION_DEBUG("Closing onion_listen"); for (i=0;i<o->nthreads-1;i++){ onion_low_pthread_join(o->threads[i],NULL); } } else #endif onion_poller_poll(o->poller); listen_points=o->listen_points; while (*listen_points){ onion_listen_point *p=*listen_points; if (p->listenfd>0){ ONION_DEBUG("Removing %d from poller", p->listenfd); onion_poller_remove(o->poller, p->listenfd); } listen_points++; } } o->flags=o->flags & ~O_LISTENING; return 0; }