/// Reads a string until a '\n|\r\n' is found. Returns an onion_token. int token_read_LINE(onion_token *token, onion_buffer *data){ if (data->pos>=data->size) return OCS_NEED_MORE_DATA; char c=data->data[data->pos++]; int ignore_to_end=0; while (c!='\n'){ if (!ignore_to_end && (token->pos>=(sizeof(token->str)-1))){ ONION_WARNING("Token too long to parse it. Ignoring remaining. "); #ifdef __DEBUG__ char tmp[16]; strncpy(tmp, token->str, 16); tmp[15]='\0'; ONION_DEBUG("Long token starts with: %s...",tmp); #endif ignore_to_end=1; } if (!ignore_to_end) token->str[token->pos++]=c; if (data->pos>=data->size) return OCS_NEED_MORE_DATA; c=data->data[data->pos++]; } if (token->str[token->pos-1]=='\r') token->str[token->pos-1]='\0'; else token->str[token->pos]='\0'; //token->pos=0; //ONION_DEBUG0("Found LINE token %s",token->str); return LINE; }
int onion_assets_file_free(onion_assets_file *f){ FILE *file=fopen(f->filename,"wt"); if (!file){ ONION_WARNING("Could not open %s asset file for updating.", f->filename); return -1; } int i; for (i=0;i<f->lines_count;i++){ ONION_DEBUG("Write: %s", f->lines[i]); ssize_t length=strlen(f->lines[i]); ssize_t wlength=fwrite(f->lines[i], 1, length, file); if (wlength!=length){ ONION_ERROR("Could not write all data. Aborting"); abort(); } wlength=fwrite("\n",1, 1, file); if (wlength!=1){ ONION_ERROR("Could not write all data. Aborting"); abort(); } free(f->lines[i]); } free(f->lines); if (f->has_endif) fprintf(file, "#endif\n"); assert(fclose(file)==0); free(f->filename); free(f); return 0; }
/** * @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)); if (!o){ return NULL; } 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; }
/// @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"); }
/// Plexes the request depending on arguments. int oterm_get_data(oterm_data *data, onion_request *req, onion_response *res){ const char *username=onion_request_get_session(req,"username"); if (!username){ ONION_WARNING("Trying to enter authenticated area without username."); return OCS_FORBIDDEN; } oterm_session *o=(oterm_session*)onion_dict_get(data->sessions, onion_request_get_session(req,"username")); if (!o){ o=oterm_session_new(); onion_dict_lock_write(data->sessions); onion_dict_add(data->sessions,onion_request_get_session(req,"username"),o, 0); onion_dict_unlock(data->sessions); } const char *path=onion_request_get_path(req); ONION_DEBUG("Ask path %s (%p)", path, data); if (strcmp(path,"new")==0){ if (onion_request_get_post(req, "command")){ free(data->exec_command); data->exec_command=strdup(onion_request_get_post(req, "command")); } oterm_new(data, o, onion_request_get_session(req, "username"), onion_request_get_session(req, "nopam") ? 0 : 1 ); return onion_shortcut_response("ok", 200, req, res); } if (strcmp(path,"status")==0) return oterm_status(o,req, res); return OCS_NOT_PROCESSED; }
/** * @short Initializes the global random number generator * * Initializes the global random number generator with some random seed. * * onion_random_free() must be called later to free up used memory. * * It is safe to call onion_random_init() more than once, but union_random_free() must be called the same amount of times. */ void onion_random_init() { onion_random_refcount_mutex_lock(); if (onion_random_refcount == 0) { int fd = open("/dev/random", O_RDONLY); if (fd < 0) { ONION_WARNING ("Unsecure random number generation; could not open /dev/random to feed the seed"); // Just in case nobody elses do it... If somebody else do it, then no problem. srand(time(NULL)); } else { unsigned int sr; ssize_t n; n = read(fd, &sr, sizeof(sr)); if (n != sizeof(sr)) { ONION_ERROR ("Error reading seed value from /dev/random after file descriptor opened"); exit(1); } else { close(fd); srand(sr); } } } onion_random_refcount++; onion_random_refcount_mutex_unlock(); }
/** * @short Do the real authorization. Checks if access allowed */ int authorize(const char *pamname, const char *username, const char *password){ int ok; pam_handle_t *pamh=NULL; const char *password_local=password; //strdup(password); struct pam_conv conv = { authPAM_passwd, (void*)password_local }; ok=pam_start(pamname, username, &conv, &pamh); if (ok==PAM_SUCCESS) ok = pam_authenticate(pamh, 0); /* is user really user? */ if (ok==PAM_SUCCESS) ok = pam_acct_mgmt(pamh, 0); /* permitted access? */ if (pam_end(pamh, ok)!=PAM_SUCCESS){ ONION_ERROR("Error releasing PAM structures"); } if (ok==PAM_SUCCESS){ ONION_DEBUG("Authenticated user %s OK", username); return 1; } ONION_WARNING("NOT authenticated user '%s', code %d", username, ok); return 0; }
/** * @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; }
/** * @short Sets the header length. Normally it should be through set_header, but as its very common and needs some procesing here is a shortcut * @memberof onion_response_t */ void onion_response_set_length(onion_response *res, size_t len){ if (len!=res->sent_bytes && res->flags&OR_HEADER_SENT){ ONION_WARNING("Trying to set length after headers sent. Undefined onion behaviour."); return; } char tmp[16]; sprintf(tmp,"%lu",(unsigned long)len); onion_response_set_header(res, "Content-Length", tmp); res->length=len; res->flags|=OR_LENGTH_SET; }
/** * @short If no root handler is set, creates an url handler and returns it. * @memberof onion_t * * It can also check if the current root handler is a url handler, and if it is, returns it. Else returns NULL. */ onion_url *onion_root_url(onion *server){ if (server->root_handler){ if (server->root_handler->priv_data_free==(void*)onion_url_free_data) // Only available check return (onion_url*)server->root_handler; ONION_WARNING("Could not get root url handler, as there is another non url handler at root."); return NULL; } ONION_DEBUG("New root url handler"); onion_url *url=onion_url_new(); server->root_handler=(onion_handler*)url; return url; }
/** * @short Returns the session dict. * @memberof onion_request_t * * If it does not exists it creates it. If there is a cookie with a proper name it is used, * even for creation. * * Sessions HAVE TO be gotten before sending any header, or user may face double sessionid, ghost sessions and some other * artifacts, as the cookie is not set if session is not used. If this condition happen (send headers and then ask for session) a * WARNING is written. * * Session is not automatically retrieved as it is a slow operation and not used normally, only on "active" handlers. * * Returned dictionary can be freely managed (added new keys...) and this is the session data. * * @return session dictionary for current request. */ onion_dict *onion_request_get_session_dict(onion_request *req) { if (!req->session) { if (req->flags & OR_HEADER_SENT) { ONION_WARNING("Asking for session AFTER sending headers. This may result in double sessionids, and wrong session behaviour. Please modify your handlers to ask for session BEFORE sending any data."); } onion_request_guess_session_id(req); if (!req->session) { // Maybe old session is not to be used anymore req->session_id=onion_sessions_create(req->connection.listen_point->server->sessions); req->session=onion_sessions_get(req->connection.listen_point->server->sessions, req->session_id); } } return req->session; }
/** * @short Moves a resource */ onion_connection_status onion_webdav_move(const char *filename, onion_webdav *wd, onion_request *req, onion_response *res){ const char *dest=onion_request_get_header(req,"Destination"); if (!dest) return OCS_INTERNAL_ERROR; const char *dest_orig=dest; // Skip the http... part. Just 3 /. int i; for (i=0;i<3;i+=(*dest++=='/')) if (*dest==0) return OCS_INTERNAL_ERROR; dest--; const char *fullpath=onion_request_get_fullpath(req); const char *partialpath=onion_request_get_path(req); // Not the fixed URL part for this handler. int fpl=strlen(fullpath); // Full path length int ppl=strlen(onion_request_get_path(req)); // Partial, the fullpath[fpl-ppl] is the end point of the handler path if (strncmp(fullpath, dest, fpl-ppl)!=0){ char tmp[512]; int l=fpl-ppl < sizeof(tmp)-1 ? fpl-ppl : sizeof(tmp)-1; strncpy(tmp, fullpath, l); tmp[l]=0; ONION_WARNING("Move to out of this webdav share! (%s is out of %s)", dest, tmp); return onion_shortcut_response("Moving out of shared share", HTTP_FORBIDDEN, req, res); } dest=&dest[fpl-ppl]; char orig[512]; snprintf(orig, sizeof(orig), "%s/%s", wd->path, partialpath); if (wd->check_permissions(wd->path, orig, req)!=0){ return onion_shortcut_response("Forbidden", HTTP_FORBIDDEN, req, res); } const char *fdest=filename; ONION_INFO("Move %s to %s (webdav)", fullpath, dest_orig); int ok=onion_shortcut_rename(orig, fdest); if (ok==0){ ONION_DEBUG("Created %s succesfully", fdest); return onion_shortcut_response("201 Created", 201, req, res); } else{ ONION_ERROR("Could not rename %s to %s (%s)", orig, fdest, strerror(errno)); return onion_shortcut_response("Could not create resource", HTTP_FORBIDDEN, req, res); } }
/// Input data to the process int oterm_in(process *p, onion_request *req, onion_response *res){ oterm_check_running(p); const char *data; data=onion_request_get_post(req,"type"); ssize_t w; if (data){ //fprintf(stderr,"%s:%d write %ld bytes\n",__FILE__,__LINE__,strlen(data)); size_t r=strlen(data); w=write(p->fd, data, r); if (w!=r){ ONION_WARNING("Error writing data to process. Not all data written. (%d).",w); return onion_shortcut_response("Error", HTTP_INTERNAL_ERROR, req, res); } } return onion_shortcut_response("OK", HTTP_OK, req, res); }
/** * @short Removes a file descriptor, and all related callbacks from the listening queue * @memberof onion_poller_t * @ingroup poller */ int onion_poller_remove(onion_poller *poller, int fd){ if (epoll_ctl(poller->fd, EPOLL_CTL_DEL, fd, NULL) < 0){ if (errno!=ENOENT && errno!=EBADF){ ONION_ERROR("Error remove descriptor to listen to. %s (%d)", strerror(errno), errno); } } pthread_mutex_lock(&poller->mutex); ONION_DEBUG0("Trying to remove fd %d (%d)", fd, poller->n); onion_poller_slot *el=poller->head; if (el && el->fd==fd){ ONION_DEBUG0("Removed from head %p", el); poller->head=el->next; pthread_mutex_unlock(&poller->mutex); onion_poller_slot_free(el); return 0; } while (el->next){ if (el->next->fd==fd){ ONION_DEBUG0("Removed from tail %p",el); onion_poller_slot *t=el->next; el->next=t->next; if (poller->head->next==NULL){ // This means only eventfd is here. ONION_DEBUG0("Removed last, stopping poll"); onion_poller_stop(poller); } pthread_mutex_unlock(&poller->mutex); onion_poller_slot_free(t); return 0; } el=el->next; } pthread_mutex_unlock(&poller->mutex); ONION_WARNING("Trying to remove unknown fd from poller %d", fd); return 0; }
/** * @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."); } if (!(flags&O_NO_SIGPIPE)){ ONION_DEBUG("Ignoring SIGPIPE"); signal(SIGPIPE, SIG_IGN); } onion *o=onion_low_calloc(1,sizeof(onion)); if (!o){ return NULL; } o->flags=(flags&0x0FF)|O_SSL_AVAILABLE; o->timeout=5000; // 5 seconds of timeout, default. o->poller=onion_poller_new(15); if (!o->poller){ onion_low_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_AVAILABLE; o->nthreads=8; if (o->flags&O_THREADED) o->flags|=O_THREADS_ENABLED; pthread_mutex_init (&o->mutex, NULL); #endif if (!(o->flags&O_NO_SIGTERM)){ signal(SIGINT,shutdown_server); signal(SIGTERM,shutdown_server); } last_onion=o; return o; }
/** * @short Executes each script file passed as argument. * * Optionally a -r sets the new lines to \r\n. It takes care of not changing content types. */ int main(int argc, char **argv){ server=onion_server_new(); onion_server_set_root_handler(server,onion_handler_new((void*)allinfo_handler,NULL,NULL)); onion_server_set_write(server,(void*)buffer_append); int i; int do_r=0; for (i=1;i<argc;i++){ if (strcmp(argv[i],"-r")==0){ ONION_WARNING("Setting the end of lines to \\r\\n"); do_r=1; } else{ ONION_INFO("Launching test %s",argv[i]); prerecorded(argv[i], do_r); } } onion_server_free(server); END(); }
onion_assets_file *onion_assets_file_new(const char *filename){ onion_assets_file *ret=malloc(sizeof(onion_assets_file)); ret->file=fopen(filename, "rt"); ret->lines_count=0; ret->lines_capacity=16; ret->lines=malloc(sizeof(const char *)*ret->lines_capacity); ret->has_endif=0; if (!ret->file){ ret->file=fopen(filename,"wt"); if (!ret->file){ ONION_WARNING("Could not open %s asset file for updating.", filename); return NULL; } onion_assets_file_add_line(ret,"/* Autogenerated by onion assets */"); onion_assets_file_add_line(ret,"#ifndef __ONION_ASSETS_H__"); onion_assets_file_add_line(ret,"#define __ONION_ASSETS_H__"); onion_assets_file_add_line(ret,"#endif"); } else{ char buffer[4096]; int r; int o=0; do{ r=fread(&buffer[o],1, sizeof(buffer)-o, ret->file); int i; o=0; for (i=0;i<r;i++){ if (buffer[i]=='\n'){ buffer[i]=0; onion_assets_file_add_line(ret, &buffer[o]); o=i+1; } } assert(o!=0 && r>0); // "Line is longer than buffer size or EOF without EOL."); }while(r<0); fclose(ret->file); ret->file=fopen(filename,"wt"); } return ret; }
int main(int argc, char **argv){ o=onion_new(O_THREADED); signal(SIGINT, free_onion); onion_set_root_handler(o, onion_handler_export_local_new(".")); onion_add_listen_point(o, "localhost", "8080", onion_http_new()); onion_add_listen_point(o, "localhost", "8081", onion_http_new()); #ifdef HAVE_GNUTLS onion_add_listen_point(o, "localhost", "4443", onion_https_new(O_SSL_CERTIFICATE_KEY, "cert.pem", "cert.key")); #else ONION_WARNING("HTTPS support is not enabled. Recompile with gnutls"); #endif /** onion_set_port(o, "localhost", "6121", onion_protocol_spdy()); */ onion_listen(o); onion_free(o); return 0; }
/** * @short Sets a new cookie into the response. * @ingroup response * * @param res Response object * @param cookiename Name for the cookie * @param cookievalue Value for the cookis * @param validity_t Seconds this cookie is valid (added to current datetime). -1 to do not expire, 0 to expire inmediatly. * @param path Cookie valid only for this path * @param Domain Cookie valid only for this domain (www.example.com, or *.example.com). * @param flags Flags from onion_cookie_flags_t, for example OC_SECURE or OC_HTTP_ONLY * * @returns boolean indicating succesfully added the cookie or not. * * If validity is 0, cookie is set to expire right now. * * If the cookie is too long (all data > 4096), it is not added. A warning is * emmited and returns false. */ bool onion_response_add_cookie(onion_response * res, const char *cookiename, const char *cookievalue, time_t validity_t, const char *path, const char *domain, int flags) { char data[4096]; int pos; pos = snprintf(data, sizeof(data), "%s=%s", cookiename, cookievalue); if (validity_t == 0) pos += snprintf(data + pos, sizeof(data) - pos, "; expires=Thu, 01 Jan 1970 00:00:00 GMT"); else if (validity_t > 0) { struct tm *tmp; time_t t = time(NULL) + validity_t; tmp = localtime(&t); pos += strftime(data + pos, sizeof(data) - pos, "; expires=%a, %d %b %Y %H:%M:%S %Z", tmp); } if (path) pos += snprintf(data + pos, sizeof(data) - pos, "; path=%s", path); if (domain) pos += snprintf(data + pos, sizeof(data) - pos, "; domain=%s", domain); if (flags & OC_HTTP_ONLY) pos += snprintf(data + pos, sizeof(data) - pos, "; HttpOnly"); if (flags & OC_SECURE) pos += snprintf(data + pos, sizeof(data) - pos, "; Secure"); if (pos >= sizeof(data)) { ONION_WARNING("Cookie too long to be constructed. Not added to response."); return false; } onion_response_set_header(res, "Set-Cookie", data); ONION_DEBUG("Set cookie %s", data); return true; }
int onion_handler_export_local_handler(onion_handler_export_local_data *d, onion_request *request, onion_response *response){ char tmp[PATH_MAX]; char realp[PATH_MAX]; if (d->is_file) strncpy(tmp, d->localpath, PATH_MAX); else snprintf(tmp,PATH_MAX, "%s/%s",d->localpath,onion_request_get_path(request)); ONION_DEBUG0("Get %s (base %s)",tmp, d->localpath); // First check if it exists and so on. If it does not exist, no trying to escape message struct stat reals; int ok=stat(tmp,&reals); if (ok<0) // Cant open for even stat { ONION_DEBUG0("Not found %s.", tmp); return 0; } const char *ret=realpath(tmp, realp); if (!ret || strncmp(realp, d->localpath, strlen(d->localpath))!=0){ // out of secured dir. ONION_WARNING("Trying to escape from secured dir (secured dir %s, trying %s).", d->localpath, realp); return 0; } if (S_ISDIR(reals.st_mode)){ //ONION_DEBUG("DIR"); return onion_handler_export_local_directory(d, realp, onion_request_get_path(request), request, response); } else if (S_ISREG(reals.st_mode)){ //ONION_DEBUG("FILE"); return onion_shortcut_response_file(realp, request, response); } ONION_DEBUG0("Dont know how to handle"); return OCS_NOT_PROCESSED; }
/** * @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; }
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 { 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; }
// Shows a warning on the logs static void warning(png_struct *p, const char *msg){ ONION_WARNING("%s", msg); }
/** * @short Reads the /etc/mime.types file * * */ static void onion_mime_fill(){ onion_mime_set(NULL); onion_mime_dict=onion_dict_new(); //ONION_DEBUG("Filling mime types"); FILE *fd=fopen("/etc/mime.types", "rt"); if (!fd){ ONION_WARNING("Could not read MIME types (etc/mime.types), returned mime types may be incorrect. Adding minimal set."); onion_dict_add(onion_mime_dict, "html", "text/html",0); onion_dict_add(onion_mime_dict, "htm", "text/html",0); onion_dict_add(onion_mime_dict, "js", "application/javascript",0); onion_dict_add(onion_mime_dict, "css", "text/css",0); onion_dict_add(onion_mime_dict, "png", "image/png",0); onion_dict_add(onion_mime_dict, "jpg", "image/jpeg",0); return; } char mimetype[128]; char extension[8]; int mode=0; // 0 mimetype, 1 extension int i=0; int c; while ( (c=getc(fd)) >= 0){ if (c=='#'){ while ( (c=getc(fd)) >= 0 && c!='\n'); } if (c=='\n'){ if (mode==1 && i!=0){ extension[i]=0; onion_dict_add(onion_mime_dict, extension, mimetype, OD_DUP_ALL); //ONION_DEBUG("Add mimetype '%s' (%s).", extension, mimetype); } mode=0; i=0; } else{ if (is_space(c)){ if (mode==0){ mimetype[i]='\0'; mode=1; i=0; } else if (i!=0){ extension[i]='\0'; i=0; onion_dict_add(onion_mime_dict, extension, mimetype, OD_DUP_ALL); //ONION_DEBUG("Add mimetype '%s' (%s)", extension, mimetype); } } else{ if (mode==0){ if (i>=sizeof(mimetype)-1){ while ( (c=getc(fd)) >= 0 && c!='\n'); } else mimetype[i++]=c; } else{ if (i>=sizeof(extension)-1){ while ( (c=getc(fd)) >= 0 && c!='\n'); extension[i]='\0'; i=0; mode=0; onion_dict_add(onion_mime_dict, extension, mimetype, OD_DUP_ALL); //ONION_DEBUG("Add mimetype '%s' (%s)..", extension, mimetype); } else extension[i++]=c; } } } } fclose(fd); ONION_DEBUG("I know %d mime types", onion_dict_count(onion_mime_dict)); }
/** * @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. */ onion_connection_status 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; } onion_response_write_headers(res); if ((onion_request_get_flags(request)&OR_HEAD) == OR_HEAD){ // Just head. length=0; } if (length){ #ifdef USE_SENDFILE if (request->connection.listen_point->write==(void*)onion_http_write){ // Lets have a house party! I can use sendfile! onion_response_write(res,NULL,0); ONION_DEBUG("Using sendfile"); int r=sendfile(request->connection.fd, 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; }
/** * @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; pthread_create(&o->listen_thread,NULL, (void*)onion_listen, o); 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 Performs the listening with the given mode * @memberof onion_t * * This is the main loop for the onion server. * * @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_DETACH_LISTEN && !(o->flags&O_DETACHED)){ // On first call it sets the variable, and then call again, this time detached. o->flags|=O_DETACHED; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // It do not need to pthread_join. No leak here. pthread_t listen_thread; pthread_create(&listen_thread, &attr,(void*(*)(void*)) onion_listen, o); pthread_attr_destroy(&attr); return 0; } #endif int sockfd=0; #ifdef HAVE_SYSTEMD if (o->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."); } sockfd=SD_LISTEN_FDS_START+0; } } #endif if (sockfd==0){ struct addrinfo hints; struct addrinfo *result, *rp; 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; if (getaddrinfo(o->hostname, o->port, &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(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 (sockfd<0) // not valid continue; 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 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",address,&address[32]); #endif freeaddrinfo(result); listen(sockfd,5); // queue of only 5. } o->listenfd=sockfd; // Drops priviledges as it has binded. if (o->username){ struct passwd *pw; pw=getpwnam(o->username); int error; if (!pw){ ONION_ERROR("Cant find user to drop priviledges: %s", o->username); return errno; } else{ error=initgroups(o->username, pw->pw_gid); error|=setgid(pw->pw_gid); error|=setuid(pw->pw_uid); } if (error){ ONION_ERROR("Cant set the uid/gid for user %s", o->username); return errno; } } if (o->flags&O_POLL){ #ifdef HAVE_PTHREADS o->poller=onion_poller_new(o->max_threads+1); #else o->poller=onion_poller_new(8); #endif onion_poller_add(o->poller, onion_poller_slot_new(o->listenfd, (void*)onion_accept_request, o)); // O_POLL && O_THREADED == O_POOL. Create several threads to poll. #ifdef HAVE_PTHREADS if (o->flags&O_THREADED){ ONION_WARNING("Pool mode is experimental. %d threads.", o->max_threads); pthread_t *thread=(pthread_t*)malloc(sizeof(pthread_t)*(o->max_threads-1)); int i; for(i=0;i<o->max_threads-1;i++){ pthread_create(&thread[i], NULL, onion_poller_adaptor, o); } onion_poller_poll(o->poller); ONION_DEBUG("Stopped poll"); for(i=0;i<o->max_threads-1;i++){ //pthread_cancel(thread[i]); // Cancel is WRONG! It left sometimes mutex without unlock, wich made deadlocks. For example. pthread_join(thread[i], NULL); } free(thread); } else #endif { ONION_WARNING("Poller mode is experimental."); onion_poller_poll(o->poller); } } else if (o->flags&O_ONE){ if ((o->flags&O_ONE_LOOP) == O_ONE_LOOP){ while(o->listenfd>0){ // Loop while listening onion_accept_request(o); } } else{ ONION_DEBUG("Listening just one connection"); onion_accept_request(o); } } #ifdef HAVE_PTHREADS else if (o->flags&O_THREADS_ENABLED){ pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // It do not need to pthread_join. No leak here. while(1){ struct sockaddr_storage cli_addr; socklen_t cli_len; int clientfd=onion_accept(o, &cli_addr, &cli_len); if (clientfd<0) return errno; // If more than allowed processes, it waits here blocking socket, as it should be. // __DEBUG__ #if 0 int nt; sem_getvalue(&o->thread_count, &nt); ONION_DEBUG("%d threads working, %d max threads", o->max_threads-nt, o->max_threads); #endif sem_wait(&o->thread_count); // Has to be malloc'd. If not it wil be overwritten on next petition. The threads frees it onion_request_thread_data *data=malloc(sizeof(onion_request_thread_data)); data->o=o; data->clientfd=clientfd; data->client_addr=cli_addr; data->client_len=cli_len; pthread_create(&data->thread_handle, &attr, onion_request_thread, data); } pthread_attr_destroy(&attr); } #endif close(o->listenfd); return 0; }
// Not implemented for libev void onion_poller_set_queue_size_per_thread(onion_poller *poller, size_t count){ ONION_WARNING("onion_poller_queue_size_per_thread only used with epoll polling, not libev."); }