/// Gets the poller to do some modifications as change shutdown onion_poller_slot *onion_poller_get(onion_poller *poller, int fd){ ONION_ERROR("Not implemented! Use epoll poller."); return NULL; }
/** * @short Creates a new listen point with HTTPS powers. * @memberof onion_https_t * * Creates the HTTPS listen point. * * Might be called with (O_SSL_NONE,NULL), and set up the certificate later with onion_https_set_certificate. * * @param type Type of certificate to setup * @param filename File from where to get the data * @param ... More types and filenames until O_SSL_NONE. * @returns An onion_listen_point with the desired data, ready to start listening. */ onion_listen_point *onion_https_new(){ onion_listen_point *op=onion_listen_point_new(); op->request_init=onion_https_request_init; op->free_user_data=onion_https_free_user_data; op->listen_stop=onion_https_listen_stop; op->read=onion_https_read; op->write=onion_https_write; op->close=onion_https_close; op->read_ready=onion_http_read_ready; op->secure = true; op->user_data=onion_low_calloc(1,sizeof(onion_https)); onion_https *https=(onion_https*)op->user_data; #ifdef HAVE_PTHREADS gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); #endif //if (!(o->flags&O_USE_DEV_RANDOM)){ gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM, 0); //} gnutls_global_init (); gnutls_certificate_allocate_credentials (&https->x509_cred); // set cert here?? //onion_https_set_certificate(op,O_SSL_CERTIFICATE_KEY, "mycert.pem","mycert.pem"); int e; int bits = gnutls_sec_param_to_pk_bits (GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LOW); e=gnutls_dh_params_init (&https->dh_params); if (e<0){ ONION_ERROR("Error initializing HTTPS: %s", gnutls_strerror(e)); gnutls_certificate_free_credentials (https->x509_cred); op->free_user_data=NULL; onion_listen_point_free(op); onion_low_free(https); return NULL; } e=gnutls_dh_params_generate2 (https->dh_params, bits); if (e<0){ ONION_ERROR("Error initializing HTTPS: %s", gnutls_strerror(e)); gnutls_certificate_free_credentials (https->x509_cred); op->free_user_data=NULL; onion_listen_point_free(op); onion_low_free(https); return NULL; } e=gnutls_priority_init (&https->priority_cache, "PERFORMANCE:%SAFE_RENEGOTIATION:-VERS-TLS1.0", NULL); if (e<0){ ONION_ERROR("Error initializing HTTPS: %s", gnutls_strerror(e)); gnutls_certificate_free_credentials (https->x509_cred); gnutls_dh_params_deinit(https->dh_params); op->free_user_data=NULL; onion_listen_point_free(op); onion_low_free(https); return NULL; } gnutls_certificate_set_dh_params (https->x509_cred, https->dh_params); gnutls_priority_init (&https->priority_cache, "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.0:+VERS-SSL3.0:%COMPAT", NULL); // PERFORMANCE:%SAFE_RENEGOTIATION:-VERS-TLS1.0:%COMPAT" ONION_DEBUG("HTTPS connection ready"); return op; }
/// Removes a fd from the poller int onion_poller_remove(onion_poller *poller, int fd){ ONION_ERROR("FIXME!! not removing fd %d", fd); return -1; }
/** * @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=pthread_create(&o->listen_thread,NULL, (void*)onion_listen, 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; } 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=malloc(sizeof(pthread_t)*(o->nthreads-1)); int i; for (i=0;i<o->nthreads-1;i++){ pthread_create(&o->threads[i],NULL,(void*)onion_poller_poll, 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++){ 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++; } } return 0; }
/** * @short Do the event polling. * @memberof onion_poller_t * * It loops over polling. To exit polling call onion_poller_stop(). * * If no fd to poll, returns. */ void onion_poller_poll(onion_poller *p){ struct epoll_event event[MAX_EVENTS]; ONION_DEBUG("Start polling"); p->stop=0; #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); p->npollers++; ONION_DEBUG0("Npollers %d. %d listenings %p", p->npollers, p->n, p->head); pthread_mutex_unlock(&p->mutex); #endif int maxtime; time_t ctime; int timeout; while (!p->stop && p->head){ ctime=time(NULL); pthread_mutex_lock(&p->mutex); maxtime=onion_poller_get_next_timeout(p); pthread_mutex_unlock(&p->mutex); timeout=maxtime-ctime; if (timeout>3600) timeout=3600000; else timeout*=1000; ONION_DEBUG0("Wait for %d ms", timeout); int nfds = epoll_wait(p->fd, event, MAX_EVENTS, timeout); int ctime_end=time(NULL); ONION_DEBUG0("Current time is %d, limit is %d, timeout is %d. Waited for %d seconds", ctime, maxtime, timeout, ctime_end-ctime); ctime=ctime_end; pthread_mutex_lock(&p->mutex); { // Somebody timedout? onion_poller_slot *next=p->head; while (next){ onion_poller_slot *cur=next; next=next->next; if (cur->timeout_limit <= ctime){ ONION_DEBUG0("Timeout on %d, was %d (ctime %d)", cur->fd, cur->timeout_limit, ctime); int i; for (i=0;i<nfds;i++){ onion_poller_slot *el=(onion_poller_slot*)event[i].data.ptr; if (cur==el){ // If removed just one with event, make it ignore the event later. ONION_DEBUG0("Ignoring event as it timeouted: %d", cur->fd); event[i].data.ptr=NULL; } } onion_poller_remove(p, cur->fd); } } } pthread_mutex_unlock(&p->mutex); if (nfds<0){ // This is normally closed p->fd //ONION_DEBUG("Some error happened"); // Also spurious wakeups... gdb is to blame sometimes or any other. if(p->fd<0 || !p->head){ ONION_DEBUG("Finishing the epoll as finished: %s", strerror(errno)); #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); p->npollers--; pthread_mutex_unlock(&p->mutex); #endif return; } } int i; for (i=0;i<nfds;i++){ onion_poller_slot *el=(onion_poller_slot*)event[i].data.ptr; if (!el) continue; // Call the callback //ONION_DEBUG("Calling callback for fd %d (%X %X)", el->fd, event[i].events); int n=-1; if (event[i].events&EPOLLRDHUP){ n=-1; } else{ // I also take care of the timeout, no timeout when on the handler, it should handle it itself. el->timeout_limit=INT_MAX; #ifdef __DEBUG0__ char **bs=backtrace_symbols((void * const *)&el->f, 1); ONION_DEBUG0("Calling handler: %s (%d)",bs[0], el->fd); onion_low_free(bs); /* This cannot be onion_low_free since from backtrace_symbols. */ #endif /* Sometimes, el->f happens to be null. We want to remove this polling in that weird case. */ if (el->f) n= el->f(el->data); else n= -1; ctime=time(NULL); if (el->timeout>0) el->timeout_limit=ctime+el->timeout; } if (n<0){ onion_poller_remove(p, el->fd); } else{ ONION_DEBUG0("Re setting poller %d", el->fd); event[i].events=el->type; if (p->fd>=0){ int e=epoll_ctl(p->fd, EPOLL_CTL_MOD, el->fd, &event[i]); if (e<0){ ONION_ERROR("Error resetting poller, %s", strerror(errno)); } } } } } ONION_DEBUG("Finished polling fds"); #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); p->npollers--; ONION_DEBUG0("Npollers %d", p->npollers); pthread_mutex_unlock(&p->mutex); #endif }
/** * 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_file(onion_request *req, onion_buffer *data){ char tmp[1024]; /// Read 1024 bytes fo the read on this call, and write. If read less write as read. int tmppos=0; onion_token *token=req->parser_data; onion_multipart_buffer *multipart=(onion_multipart_buffer*)token->extra; const char *p=data->data+data->pos; for (;data->pos<data->size;data->pos++){ //ONION_DEBUG("*p %d boundary %d (%s)",*p,multipart->boundary[multipart->pos],multipart->boundary); if (multipart->pos==0){ if (*p=='\n') // \r is optional. multipart->startpos=multipart->pos=1; else multipart->startpos=0; } if (*p==multipart->boundary[multipart->pos]){ multipart->pos++; if (multipart->pos==multipart->size){ multipart->startpos=multipart->pos=0; data->pos++; // Not sure why this is needed. FIXME. int w=write(multipart->fd,tmp,tmppos); if (w!=tmppos){ ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk."); close(multipart->fd); return OCS_INTERNAL_ERROR; } close(multipart->fd); req->parser=parse_POST_multipart_next; return OCS_NEED_MORE_DATA; } } else{ if (multipart->file_total_size>req->connection.listen_point->server->max_file_size){ ONION_ERROR("Files on this post too big. Aborting."); return OCS_INTERNAL_ERROR; } if (multipart->pos!=0){ multipart->file_total_size+=multipart->pos; int r=multipart->pos-multipart->startpos; //ONION_DEBUG0("Write %d bytes",r); int w=write(multipart->fd,tmp,tmppos); if (w!=tmppos){ ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk."); close(multipart->fd); return OCS_INTERNAL_ERROR; } tmppos=0; w=write(multipart->fd, multipart->boundary+multipart->startpos, r); if (w!=r){ ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk."); close(multipart->fd); return OCS_INTERNAL_ERROR; } multipart->startpos=multipart->pos=0; data->pos--; // Ignore read charater, try again. May be start of boundary. continue; } //ONION_DEBUG0("Write 1 byte"); tmp[tmppos++]=*p; if (tmppos==sizeof(tmp)){ int w=write(multipart->fd,tmp,tmppos); if (w!=tmppos){ ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk."); close(multipart->fd); return OCS_INTERNAL_ERROR; } tmppos=0; } } multipart->file_total_size++; p++; } int w=write(multipart->fd,tmp,tmppos); if (w!=tmppos){ ONION_ERROR("Error writing multipart data to file. Check permissions on temp directory, and availabe disk."); close(multipart->fd); return OCS_INTERNAL_ERROR; } return OCS_NEED_MORE_DATA; }
/** * @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 onion_request_process(req); //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 Solves a variable into code. * * It uses the type to check it its a literal string, a dcit string or a dict. */ void variable_solve(parser_status *st, const char *data, const char *tmpname, vartype_e type){ if (type==LITERAL){ char *s=onion_c_quote_new(data); function_add_code(st, " %s=%s;\n", tmpname, s); free(s); return; } if (! (type==STRING || type==DICT) ){ ONION_ERROR("Invalid type for variable solve"); exit(1); } list *parts=list_new(onion_block_free); onion_block *lastblock; list_add(parts, lastblock=onion_block_new()); int i=0; int l=strlen(data); const char *d=data; for (i=0;i<l;i++){ if (d[i]=='.') list_add(parts, lastblock=onion_block_new()); else if (d[i]==' ') continue; else onion_block_add_char(lastblock, d[i]); } if (list_count(parts)==1){ char *s=onion_c_quote_new(onion_block_data(lastblock)); if (type==STRING) function_add_code(st, " %s=onion_dict_get(context, %s);\n", tmpname, s); else if (type==DICT) function_add_code(st, " %s=onion_dict_get_dict(context, %s);\n", tmpname, s); free(s); } else{ if (type==STRING) function_add_code(st," %s=onion_dict_rget(context", tmpname); else if (type==DICT) function_add_code(st," %s=onion_dict_rget_dict(context", tmpname); else{ ONION_ERROR("Invalid type for variable solve"); exit(1); } list_item *it=parts->head; while (it){ lastblock=it->data; char *s=onion_c_quote_new(onion_block_data(lastblock)); function_add_code(st,", %s", s); free(s); it=it->next; } function_add_code(st,", NULL);\n"); } list_free(parts); }
/// There is a bug when posting large files. Introduced when change write 1 by 1, to write by blocks on the FILE parser void t04_post_largefile(){ INIT_LOCAL(); int postfd=open(BIG_FILE, O_RDONLY); off_t filesize=lseek(postfd, 0, SEEK_END); lseek(postfd, 0, SEEK_SET); buffer *b=buffer_new(1024); expected_post post={};; post.filename=BIG_FILE_BASE; post.test_ok=0; // Not ok as not called processor yet post.tmpfilename=NULL; post.size=filesize; onion_server *server=onion_server_new(); onion_server_set_write(server, (void*)&buffer_append); onion_server_set_root_handler(server, onion_handler_new((void*)&post_check,&post,NULL)); onion_request *req=onion_request_new(server,b,"test"); #define POST_HEADER "POST / HTTP/1.1\nContent-Type: multipart/form-data; boundary=end\nContent-Length: %d\n\n--end\nContent-Disposition: text/plain; name=\"file\"; filename=\"" BIG_FILE_BASE "\"\n\n" char tmp[1024]; ONION_DEBUG("Post size is about %d",filesize+73); snprintf(tmp, sizeof(tmp), POST_HEADER, (int)filesize+73); ONION_DEBUG("%s",tmp); onion_request_write(req,tmp,strlen(tmp)); int r=read(postfd, tmp, sizeof(tmp)); while ( r>0 ){ onion_request_write(req, tmp, r); r=read(postfd, tmp, sizeof(tmp)); } onion_request_write(req,"\n--end--",8); FAIL_IF_NOT_EQUAL_INT(post.test_ok,1); #undef POST_HEADER post.test_ok=0; // Not ok as not called processor yet lseek(postfd, 0, SEEK_SET); int difffd=open(post.tmpfilename, O_RDONLY); FAIL_IF_NOT_EQUAL_INT(difffd,-1); // Orig file is removed at handler returns. But i have a copy difffd=open(post.tmplink, O_RDONLY); FAIL_IF_EQUAL_INT(difffd,-1); ONION_DEBUG("tmp filename %s",post.tmpfilename); int r1=1, r2=1; char c1=0, c2=0; int p=0; while ( r1 && r2 && c1==c2){ r1=read(difffd, &c1, 1); r2=read(postfd, &c2, 1); //ONION_DEBUG("%d %d",c1,c2); FAIL_IF_NOT_EQUAL_INT(c1,c2); p++; } if ( r1 || r2 ){ ONION_ERROR("At %d",p); FAIL_IF_NOT_EQUAL_INT(r1,0); FAIL_IF_NOT_EQUAL_INT(r2,0); FAIL_IF_NOT_EQUAL_INT(c1,c2); FAIL("Files are different"); } else ONION_DEBUG("Files are ok"); close(difffd); close(postfd); onion_request_clean(req); onion_request_free(req); if (post.tmpfilename){ struct stat st; FAIL_IF_EQUAL(stat(post.tmpfilename,&st), 0); // Should not exist } onion_server_free(server); buffer_free(b); if (post.tmpfilename) free(post.tmpfilename); if (post.tmplink) free(post.tmplink); END_LOCAL(); }
/** * @short Main parsing loop. */ void parse_template(parser_status *status){ int c; while ( (c=fgetc(status->in)) != EOF){ status->c=c; if (c=='\n') status->line++; switch(status->mode){ case TEXT: if (c=='{') set_mode(status, BEGIN); else add_char(status, c); break; case BEGIN: if (c=='{') set_mode(status, VARIABLE); else if (c=='%') set_mode(status, TAG); else{ set_mode(status, TEXT); add_char(status, '{'); add_char(status, c); } break; case VARIABLE: if (c=='}') set_mode(status, END_VARIABLE); else add_char(status, c); break; case TAG: if (c=='%') set_mode(status, END_TAG); else add_char(status, c); break; case END_VARIABLE: if (c=='}') set_mode(status, TEXT); else{ set_mode(status, VARIABLE); add_char(status, '}'); add_char(status, c); } break; case END_TAG: if (c=='}') set_mode(status, TEXT); else{ set_mode(status, END_TAG); add_char(status, '%'); add_char(status, c); } break; default: ONION_ERROR("Unknown mode %d",status->mode); status->status=1; return; } } set_mode(status, END); }
/** * Current block is a tag, slice it and call the proper handler. */ void tag_write(parser_status *st, onion_block *b){ //ONION_DEBUG("Write tag %s",b->data); list *command=list_new((void*)tag_token_free); char mode=0; // 0 skip spaces, 1 in single var, 2 in quotes // Split into elements for the list int i, li=0; const char *data=onion_block_data(b); int size=onion_block_size(b); for (i=0;i<size;i++){ char c=data[i]; switch(mode){ case 0: if (!isspace(c)){ if (c=='"'){ mode=2; li=i+1; } else{ mode=1; li=i; } } break; case 1: if (isspace(c)){ mode=0; list_add(command, tag_token_new(&data[li], i-li, T_VAR)); } break; case 2: if (c=='"'){ mode=0; list_add(command, tag_token_new(&data[li], i-li, T_STRING)); } break; } } if (mode==1) list_add(command, tag_token_new(&data[li], i-li, T_VAR)); if (!command->head){ ONION_ERROR("%s:%d Incomplete command", st->infilename, st->line); st->status=1; return; } // Call the function. tag_token *commandtoken=command->head->data; const char *commandname=commandtoken->data; void (*f)(parser_status *st, list *args); f=(void*)onion_dict_get(tags, commandname); if (f) f(st, command); else{ ONION_ERROR("%s:%d Unknown command '%s'. ", st->infilename, st->line, commandname); st->status=1; } list_free(command); }
/** * @short Do the event polling. * @memberof onion_poller_t * @ingroup poller * * It loops over polling. To exit polling call onion_poller_stop(). * * If no fd to poll, returns. */ void onion_poller_poll(onion_poller *p){ struct epoll_event event[onion_poller_max_events]; ONION_DEBUG("Start polling"); #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); p->npollers++; p->stop=0; ONION_DEBUG0("Npollers %d. %d listenings %p", p->npollers, p->n, p->head); pthread_mutex_unlock(&p->mutex); #else p->stop=0; #endif #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); char stop = !p->stop && p->head; pthread_mutex_unlock(&p->mutex); #else char stop = !p->stop && p->head; #endif while (stop){ int nfds = epoll_wait(p->fd, event, onion_poller_max_events, -1); if (nfds<0){ // This is normally closed p->fd //ONION_DEBUG("Some error happened"); // Also spurious wakeups... gdb is to blame sometimes or any other. if(p->fd<0 || !p->head){ ONION_DEBUG("Finishing the epoll as finished: %s", strerror(errno)); #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); p->npollers--; pthread_mutex_unlock(&p->mutex); #endif return; } } int i; for (i=0;i<nfds;i++){ onion_poller_slot *el=(onion_poller_slot*)event[i].data.ptr; if (!el) continue; // Call the callback //ONION_DEBUG("Calling callback for fd %d (%X %X)", el->fd, event[i].events); int n=-1; if (event[i].events&(EPOLLRDHUP | EPOLLHUP)){ n=-1; } else{ // I also take care of the timeout, no timeout when on the handler, it should handle it itself. el->timeout_limit=INT_MAX; #ifdef __DEBUG0__ char **bs=backtrace_symbols((void * const *)&el->f, 1); ONION_DEBUG0("Calling handler: %s (%d)",bs[0], el->fd); onion_low_free(bs); /* This cannot be onion_low_free since from backtrace_symbols. */ #endif n = el->f(el->data); if (el->timeout>0){ el->timeout_limit=onion_time()+el->timeout; onion_poller_timer_check(p, el->timeout_limit); } } if (n<0){ onion_poller_remove(p, el->fd); } else{ ONION_DEBUG0("Re setting poller %d", el->fd); event[i].events=el->type; if (p->fd>=0){ int e=epoll_ctl(p->fd, EPOLL_CTL_MOD, el->fd, &event[i]); if (e<0){ ONION_ERROR("Error resetting poller, %s", strerror(errno)); } } } } #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); stop = !p->stop && p->head; pthread_mutex_unlock(&p->mutex); #else stop = !p->stop && p->head; #endif } ONION_DEBUG("Finished polling fds"); #ifdef HAVE_PTHREADS pthread_mutex_lock(&p->mutex); p->npollers--; ONION_DEBUG0("Npollers %d", p->npollers); pthread_mutex_unlock(&p->mutex); #endif }
int main(int argc, char **argv){ char *port="8080"; char *serverip="::"; const char *command="/bin/bash"; const char *certificatefile="/etc/pki/tls/certs/pound.pem"; const char *keyfile="/etc/pki/tls/certs/pound.key"; int error; int i; int ssl=1; #ifdef HAVE_PAM int use_pam=1; #endif for (i=1;i<argc;i++){ if (strcmp(argv[i],"--help")==0){ show_help(); exit(0); } else if(strcmp(argv[i],"-p")==0 || strcmp(argv[i],"--port")==0){ if (i+1>argc){ ONION_ERROR("Need to set the port number."); show_help(); exit(1); } port=argv[++i]; fprintf(stderr, "Using port %s\n",port); } else if(strcmp(argv[i],"-i")==0 || strcmp(argv[i],"--ip")==0){ if (i+1>argc){ ONION_ERROR("Need to set the ip address or hostname."); show_help(); exit(1); } serverip=argv[++i]; fprintf(stderr, "Using ip %s\n",serverip); } else if(strcmp(argv[i],"-c")==0 || strcmp(argv[i],"--cert")==0){ if (i+1>argc){ ONION_ERROR("Need to set the certificate filename"); show_help(); exit(1); } certificatefile=argv[++i]; ONION_INFO("Using certificate %s",certificatefile); } else if(strcmp(argv[i],"-k")==0 || strcmp(argv[i],"--key")==0){ if (i+1>argc){ ONION_ERROR("Need to set the certificate key filename."); show_help(); exit(1); } keyfile=argv[++i]; ONION_INFO("Using certificate key %s",keyfile); } else if(strcmp(argv[i],"-x")==0 || strcmp(argv[i],"--exec")==0){ if (i+1>argc){ ONION_ERROR("Need the command to execute."); show_help(); exit(1); } command=argv[++i]; ONION_INFO("New terminal execute the command %s",command); } else if(strcmp(argv[i],"--no-ssl")==0){ ssl=0; ONION_INFO("Disabling SSL!"); } #ifdef HAVE_PAM else if(strcmp(argv[i],"--no-pam")==0){ use_pam=0; ONION_INFO("Disabling PAM!"); } #endif } o=onion_new(O_POOL|O_SYSTEMD); // I prepare the url handler, with static, uuid and term. Also added the empty rule that redirects to static/index.html onion_url *url=onion_url_new(); onion_handler *term_handler=oterm_handler(o,command); #ifdef HAVE_PAM if (use_pam){ onion_url_add_handler(url, "^term/", onion_handler_auth_pam("Onion Terminal", "login", term_handler)); } else #endif { onion_url_add_with_data(url, "^term/", oterm_nopam, term_handler, NULL); } onion_url_add_with_data(url, "^uuid/", oterm_uuid, onion_handler_get_private_data(term_handler), NULL); #ifdef __DEBUG__ if (getenv("OTERM_DEBUG")) onion_url_add_handler(url, "^static/", onion_handler_export_local_new("static")); else #endif { #ifndef JQUERY_JS onion_url_add(url, "^static/jquery.js", opack_jquery_js); #else onion_url_add_handler(url, "^static/jquery.js", onion_handler_export_local_new(JQUERY_JS)); #endif onion_url_add(url, "^static/", opack_static); } onion_url_add_with_data(url, "", onion_shortcut_internal_redirect, "static/index.html", NULL); srand(time(NULL)); onion_set_root_handler(o, onion_url_to_handler(url)); if (!(onion_flags(o)&O_SSL_AVAILABLE)){ ONION_WARNING("SSL support is not available. Oterm is in unsecure mode!"); } else if (ssl){ // Not necesary the else, as onion_use_certificate would just return an error. But then it will exit. error=onion_set_certificate(o, O_SSL_CERTIFICATE_KEY, certificatefile, keyfile); if (error){ ONION_ERROR("Cant set certificate and key files (%s, %s)",certificatefile, keyfile); show_help(); exit(1); } } onion_set_port(o, port); onion_set_hostname(o, serverip); onion_set_timeout(o,5000); signal(SIGINT, free_onion); signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Listening at %s\n",port); error=onion_listen(o); if (error){ ONION_ERROR("Cant create the server: %s", strerror(errno)); } onion_free(o); return 0; }
/** * @short Starts the listening phase for this listen point for sockets. * @memberof onion_listen_point_t * * Default listen implementation that listens on sockets. Opens sockets and setup everything properly. * * @param op The listen point * @returns 0 if ok, !=0 some error; it will be the errno value. */ int onion_listen_point_listen(onion_listen_point *op){ if (op->listen){ op->listen(op); return 0; } #ifdef HAVE_SYSTEMD if (op->server->flags&O_SYSTEMD){ int n=sd_listen_fds(0); ONION_DEBUG("Checking if have systemd sockets: %d",n); if (n>0){ // If 0, normal startup. Else use the first LISTEN_FDS. ONION_DEBUG("Using systemd sockets"); if (n>1){ ONION_WARNING("Get more than one systemd socket descriptor. Using only the first."); } op->listenfd=SD_LISTEN_FDS_START+0; return 0; } } #endif struct addrinfo hints; struct addrinfo *result, *rp; int sockfd; memset(&hints,0, sizeof(struct addrinfo)); hints.ai_canonname=NULL; hints.ai_addr=NULL; hints.ai_next=NULL; hints.ai_socktype=SOCK_STREAM; hints.ai_family=AF_UNSPEC; hints.ai_flags=AI_PASSIVE|AI_NUMERICSERV; ONION_DEBUG("Trying to listen at %s:%s", op->hostname, op->port ? op->port : "8080"); if (getaddrinfo(op->hostname, op->port ? op->port : "8080", &hints, &result) !=0 ){ ONION_ERROR("Error getting local address and port: %s", strerror(errno)); return errno; } int optval=1; for(rp=result;rp!=NULL;rp=rp->ai_next){ sockfd=socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); if (sockfd<0) // not valid continue; if(SOCK_CLOEXEC == 0) { // Good compiler know how to cut this out int flags=fcntl(sockfd, F_GETFD); if (flags==-1){ ONION_ERROR("Retrieving flags from listen socket"); } flags|=FD_CLOEXEC; if (fcntl(sockfd, F_SETFD, flags)==-1){ ONION_ERROR("Setting O_CLOEXEC to listen socket"); } } if (setsockopt(sockfd,SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval) ) < 0){ ONION_ERROR("Could not set socket options: %s",strerror(errno)); } if (bind(sockfd, rp->ai_addr, rp->ai_addrlen) == 0) break; // Success else { ONION_ERROR("Could not bind to socket: %s",strerror(errno)); } close(sockfd); } if (rp==NULL){ ONION_ERROR("Could not find any suitable address to bind to."); return errno; } #ifdef __DEBUG__ char address[64]; getnameinfo(rp->ai_addr, rp->ai_addrlen, address, 32, &address[32], 32, NI_NUMERICHOST | NI_NUMERICSERV); ONION_DEBUG("Listening to %s:%s, fd %d",address,&address[32],sockfd); #endif freeaddrinfo(result); listen(sockfd,5); // queue of only 5. op->listenfd=sockfd; return 0; }
/** * @short Write the properties of a path. * * Given a path, and optionally a file at that path, writes the XML of its properties. * * If no file given, data is for that path, else it is for the path+filename. * * @param writer XML writer to write the data to * @param realpath The real path on the file server, used to check permissions and read data TODO * @param urlpath The base URL of the element, needed in the response. Composed with filename if filename!=NULL. * @param filename NULL if that element itself, else the subelement (file in path). * @param props Bitmask of the properties the user asked for. * * @return 0 is ok, 1 if could not stat file. */ int onion_webdav_write_props(xmlTextWriterPtr writer, const char *basepath, const char *realpath, const char *urlpath, const char *filename, int props){ ONION_DEBUG("Info for path '%s', urlpath '%s', file '%s', basepath '%s'", realpath, urlpath, filename, basepath); // Stat the thing itself char tmp[PATH_MAX]; if (filename) snprintf(tmp, sizeof(tmp), "%s/%s", realpath, filename); else{ if (strlen(realpath)>=sizeof(tmp)-1){ ONION_ERROR("File path too long"); return 1; } strncpy(tmp, realpath, sizeof(tmp)-1); } struct stat st; if (stat(tmp, &st)<0){ ONION_ERROR("Error on %s: %s", tmp, strerror(errno)); return 1; } while (*urlpath=='/') // No / at the begining. urlpath++; if (filename){ if (urlpath[0]==0) snprintf(tmp, sizeof(tmp), "%s/%s", basepath, filename); else snprintf(tmp, sizeof(tmp), "%s/%s/%s", basepath, urlpath, filename); } else{ if (urlpath[0]==0) snprintf(tmp, sizeof(tmp), "%s/", basepath); else{ snprintf(tmp, sizeof(tmp), "%s/%s", basepath, urlpath); } } if (S_ISDIR(st.st_mode) && urlpath[0]!=0) strncat(tmp,"/", sizeof(tmp) - 1); ONION_DEBUG0("Props for %s", tmp); xmlTextWriterStartElement(writer, BAD_CAST "D:response"); xmlTextWriterWriteAttribute(writer, BAD_CAST "xmlns:lp1" ,BAD_CAST "DAV:"); xmlTextWriterWriteAttribute(writer, BAD_CAST "xmlns:g0" ,BAD_CAST "DAV:"); xmlTextWriterWriteAttribute(writer, BAD_CAST "xmlns:a" ,BAD_CAST "http://apache.org/dav/props/"); xmlTextWriterWriteElement(writer, BAD_CAST "D:href", BAD_CAST tmp); /// OK xmlTextWriterStartElement(writer, BAD_CAST "D:propstat"); xmlTextWriterStartElement(writer, BAD_CAST "D:prop"); if (props&WD_RESOURCE_TYPE){ xmlTextWriterStartElement(writer, BAD_CAST "lp1:resourcetype"); if (S_ISDIR(st.st_mode)){ // no marker for other resources xmlTextWriterStartElement(writer, BAD_CAST "D:collection"); xmlTextWriterEndElement(writer); } xmlTextWriterEndElement(writer); } if (props&WD_LAST_MODIFIED){ char time[32]; onion_shortcut_date_string(st.st_mtime, time); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getlastmodified", BAD_CAST time); } if (props&WD_CREATION_DATE){ char time[32]; onion_shortcut_date_string_iso(st.st_mtime, time); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:creationdate", BAD_CAST time ); } if (props&WD_CONTENT_LENGTH && !S_ISDIR(st.st_mode)){ snprintf(tmp, sizeof(tmp), "%d", (int)st.st_size); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getcontentlength", BAD_CAST tmp); } if (props&WD_CONTENT_TYPE && S_ISDIR(st.st_mode)){ xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getcontenttype", BAD_CAST "httpd/unix-directory"); } if (props&WD_ETAG){ char etag[32]; onion_shortcut_etag(&st, etag); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getetag", BAD_CAST etag); } if (props&WD_EXECUTABLE && !S_ISDIR(st.st_mode)){ if (st.st_mode&0111) xmlTextWriterWriteElement(writer, BAD_CAST "a:executable", BAD_CAST "true"); else xmlTextWriterWriteElement(writer, BAD_CAST "a:executable", BAD_CAST "false"); } xmlTextWriterEndElement(writer); xmlTextWriterWriteElement(writer, BAD_CAST "D:status", BAD_CAST "HTTP/1.1 200 OK"); xmlTextWriterEndElement(writer); // /propstat /// NOT FOUND xmlTextWriterStartElement(writer, BAD_CAST "D:propstat"); xmlTextWriterStartElement(writer, BAD_CAST "D:prop"); if (props&WD_CONTENT_LENGTH && S_ISDIR(st.st_mode)){ snprintf(tmp, sizeof(tmp), "%d", (int)st.st_size); xmlTextWriterWriteElement(writer, BAD_CAST "g0:getcontentlength", BAD_CAST ""); } if (props&WD_CONTENT_TYPE && !S_ISDIR(st.st_mode)){ xmlTextWriterWriteElement(writer, BAD_CAST "g0:getcontenttype", BAD_CAST ""); } if (props&WD_DISPLAY_NAME){ xmlTextWriterWriteElement(writer, BAD_CAST "g0:displayname", BAD_CAST ""); } xmlTextWriterEndElement(writer); xmlTextWriterWriteElement(writer, BAD_CAST "D:status", BAD_CAST "HTTP/1.1 404 Not Found"); xmlTextWriterEndElement(writer); // /propstat xmlTextWriterEndElement(writer); return 0; }
/** * @short This shortcut returns the given file contents. * * It sets all the compilant headers (TODO), cache and so on. * * This is the recomended way to send static files; it even can use sendfile Linux call * if suitable (TODO). * * It does no security checks, so caller must be security aware. */ int onion_shortcut_response_file(const char *filename, onion_request *request, onion_response *res){ int fd=open(filename,O_RDONLY|O_CLOEXEC); if (fd<0) return OCS_NOT_PROCESSED; if(O_CLOEXEC == 0) { // Good compiler know how to cut this out int flags=fcntl(fd, F_GETFD); if (flags==-1){ ONION_ERROR("Retrieving flags from file descriptor"); } flags|=FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags)==-1){ ONION_ERROR("Setting O_CLOEXEC to file descriptor"); } } struct stat st; if (stat(filename, &st)!=0){ ONION_WARNING("File does not exist: %s",filename); close(fd); return OCS_NOT_PROCESSED; } if (S_ISDIR(st.st_mode)){ close(fd); return OCS_NOT_PROCESSED; } size_t length=st.st_size; char etag[64]; onion_shortcut_etag(&st, etag); const char *range=onion_request_get_header(request, "Range"); if (range){ strncat(etag,range,sizeof(etag)-1); } onion_response_set_header(res, "Etag", etag); if (range && strncmp(range,"bytes=",6)==0){ onion_response_set_code(res, HTTP_PARTIAL_CONTENT); //ONION_DEBUG("Need just a range: %s",range); char tmp[1024]; strncpy(tmp, range+6, 1024); char *start=tmp; char *end=tmp; while (*end!='-' && *end) end++; if (*end=='-'){ *end='\0'; end++; //ONION_DEBUG("Start %s, end %s",start,end); size_t ends, starts; if (*end) ends=atol(end); else ends=length; starts=atol(start); length=ends-starts+1; lseek(fd, starts, SEEK_SET); snprintf(tmp,sizeof(tmp),"bytes %d-%d/%d",(unsigned int)starts, (unsigned int)ends, (unsigned int)st.st_size); //onion_response_set_header(res, "Accept-Ranges","bytes"); onion_response_set_header(res, "Content-Range",tmp); } } onion_response_set_length(res, length); onion_response_set_header(res, "Content-Type", onion_mime_get(filename) ); ONION_DEBUG("Mime type is %s",onion_mime_get(filename)); ONION_DEBUG0("Etag %s", etag); const char *prev_etag=onion_request_get_header(request, "If-None-Match"); if (prev_etag && (strcmp(prev_etag, etag)==0)){ ONION_DEBUG0("Not modified"); onion_response_set_length(res, 0); onion_response_set_code(res, HTTP_NOT_MODIFIED); onion_response_write_headers(res); close(fd); return OCS_PROCESSED; } if ((onion_request_get_flags(request)&OR_HEAD) == OR_HEAD){ // Just head. length=0; } if (length){ #ifdef USE_SENDFILE if (request->server->write==(void*)onion_write_to_socket){ // Lets have a house party! I can use sendfile! onion_response_write(res,NULL,0); ONION_DEBUG("Using sendfile"); int r=sendfile((long int)request->socket, fd, NULL, length); ONION_DEBUG("Wrote %d, should be %d (%s)", r, length, r==length ? "ok" : "nok"); if (r!=length || r<0){ ONION_ERROR("Could not send all file (%s)", strerror(errno)); close(fd); return OCS_INTERNAL_ERROR; } res->sent_bytes+=length; res->sent_bytes_total+=length; } else #endif { // Ok, no I cant, do it as always. int r=0,w; size_t tr=0; char tmp[4046]; if (length>sizeof(tmp)){ size_t max=length-sizeof(tmp); while( tr<max ){ r=read(fd,tmp,sizeof(tmp)); tr+=r; if (r<0) break; w=onion_response_write(res, tmp, r); if (w!=r){ ONION_ERROR("Wrote less than read: write %d, read %d. Quite probably closed connection.",w,r); break; } } } if (sizeof(tmp) >= (length-tr)){ r=read(fd, tmp, length-tr); w=onion_response_write(res, tmp, r); if (w!=r){ ONION_ERROR("Wrote less than read: write %d, read %d. Quite probably closed connection.",w,r); } } } } close(fd); return OCS_PROCESSED; }