/** * @short Internal accept of just one request. * * It might be called straight from listen, or from the epoller. * * @returns 0 if ok, <0 if error. */ static int onion_accept_request(onion *o){ struct sockaddr_storage cli_addr; socklen_t cli_len; int clientfd=onion_accept(o, &cli_addr, &cli_len); if (clientfd<0) return -1; if (o->flags&O_POLL){ onion_request *req=onion_connection_start(o, clientfd, &cli_addr, cli_len); if (!req){ shutdown(clientfd,SHUT_RDWR); // Socket must be destroyed. close(clientfd); return 0; } onion_poller_slot *slot=onion_poller_slot_new(clientfd, (void*)onion_connection_read, req); onion_poller_slot_set_shutdown(slot, (void*)onion_connection_shutdown, req); onion_poller_slot_set_timeout(slot, o->timeout); onion_poller_add(o->poller, slot); } else{ onion_process_request(o, clientfd, &cli_addr, cli_len); } 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; }
/** * @short Called when a new connection appears on the listenfd * @memberof onion_listen_point_t * * When the new connection appears, creates the request and adds it to the pollers. * * It returns always 1 as any <0 would detach from the poller and close the listen point, * and not accepting a request does not mean the connection point is corrupted. If a * connection point may become corrupted should be the connection point itself who detaches * from the poller. * * @param op The listen point from where the request must be built * @returns 1 always. */ int onion_listen_point_accept(onion_listen_point *op){ onion_request *req=onion_request_new(op); if (req){ if (req->connection.fd>0){ onion_poller_slot *slot=onion_poller_slot_new(req->connection.fd, (void*)onion_listen_point_read_ready, req); if (!slot) return 1; onion_poller_slot_set_timeout(slot, req->connection.listen_point->server->timeout); onion_poller_slot_set_shutdown(slot, (void*)onion_request_free, req); onion_poller_add(req->connection.listen_point->server->poller, slot); return 1; } if (req->connection.fd<0) ONION_ERROR("Error creating connection"); // else, no need for fd... no use case yet. } return 1; }
/** * @short Called when a new connection appears on the listenfd * @memberof onion_listen_point_t * * When the new connection appears, creates the request and adds it to the pollers. * * It returns always 1 as any <0 would detach from the poller and close the listen point, * and not accepting a request does not mean the connection point is corrupted. If a * connection point may become corrupted should be the connection point itself who detaches * from the poller. * * @param op The listen point from where the request must be built * @returns 1 always. The poller needs one to keep listening for connections. */ int onion_listen_point_accept(onion_listen_point *op){ onion_request *req=onion_request_new(op); if (req){ if (req->connection.fd>0){ onion_poller_slot *slot=onion_poller_slot_new(req->connection.fd, (void*)onion_listen_point_read_ready, req); if (!slot) return 1; onion_poller_slot_set_timeout(slot, req->connection.listen_point->server->timeout); onion_poller_slot_set_shutdown(slot, (void*)onion_request_free, req); onion_poller_add(req->connection.listen_point->server->poller, slot); return 1; } // No fd. This could mean error, or not fd based. Normally error would not return a req. onion_request_free(req); ONION_ERROR("Error creating connection"); return 1; } 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; 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; }
/// Creates a new oterm process *oterm_new(oterm_data *data, oterm_session *session, const char *username, char impersonate){ process *oterm=malloc(sizeof(process)); const char *command_name; int i; for (i=strlen(data->exec_command);i>=0;i--) if (data->exec_command[i]=='/') break; command_name=&data->exec_command[i+1]; /// Get the UUID, linux nicely gives it. { int fd=open("/proc/sys/kernel/random/uuid", O_RDONLY); if (fd>=0){ int r=read(fd, oterm->uuid, sizeof(oterm->uuid)-1); close(fd); if (r!=sizeof(oterm->uuid)-1) // So we will use the pseudo random generator. fd=-1; } if (fd<0){ const char random_chars[]="0123456789abcdef-"; for (i=0;i<sizeof(oterm->uuid)-1;i++){ oterm->uuid[i]=random_chars[rand()%sizeof(random_chars)]; } } oterm->uuid[sizeof(oterm->uuid)-1]=0; ONION_DEBUG("New UUID for this terminal is %s", oterm->uuid); } oterm->buffer=calloc(1, BUFFER_SIZE); oterm->buffer_pos=0; pthread_mutex_init(&oterm->mutex, NULL); pthread_cond_init(&oterm->dataReady, NULL); ONION_DEBUG("Creating new terminal, exec %s (%s)", data->exec_command, command_name); oterm->pid=forkpty(&oterm->fd, NULL, NULL, NULL); if ( oterm->pid== 0 ){ // on child // Copy env vars. char **envs=malloc(sizeof(char*)*(1+ONION_CLEARENV_COUNT+ONION_EXTRAENV_COUNT)); int i,j=0; for (i=0;i<ONION_CLEARENV_COUNT;i++){ const char *env=onion_clearenvs[i]; const char *val=getenv(env); if (val){ int l=strlen(env)+1+strlen(val)+1; envs[j]=malloc(l); sprintf(envs[j],"%s=%s",env,val); j++; } } for (i=0;i<ONION_EXTRAENV_COUNT;i++){ envs[j]=strdup(onion_extraenvs[i]); j++; } envs[j]=NULL; // Change personality to that user if (impersonate){ struct passwd *pw; pw=getpwnam(username); int error; if (!pw){ ONION_ERROR("Cant find user to drop priviledges: %s", username); exit(1); } else{ error=setgid(pw->pw_gid); error|=setuid(pw->pw_uid); } if (error){ ONION_ERROR("Cant set the uid/gid for user %s", username); exit(1); } } for (i=3;i<256;i++) // Force close file descriptors. Dirty but it works. close(i); int ok=execle(data->exec_command, command_name, NULL, envs); fprintf(stderr,"%s:%d Could not exec shell: %d\n",__FILE__,__LINE__,ok); perror(""); exit(1); } oterm->title=strdup(data->exec_command); ONION_DEBUG("Default title is %s", oterm->title); oterm->next=NULL; // I set myself at end pthread_mutex_lock( &session->head_mutex ); if (!session->head) session->head=oterm; else{ process *next=session->head; while (next->next) next=next->next; next->next=oterm; } onion_poller_slot *sl=onion_poller_slot_new(oterm->fd, (void*)oterm_data_ready, oterm); onion_poller_add(onion_get_poller(data->onion), sl); pthread_mutex_unlock( &session->head_mutex ); onion_dict_add(data->processes, oterm->uuid, oterm, 0); return oterm; }