/* ARISA - Admin Connectivity * Copyright (C) 2003, 2004 Carl Ritson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #define WANT_POLL #include "arisa.h" #include "user.h" #include #include #ifdef HAVE_SYS_UN_H #include #ifndef UNIX_PATH_MAX /* On Linux its 108 on FreeBSD 104, Help?! */ #define UNIX_PATH_MAX 100 #endif /* !UNIX_PATH_MAX */ #endif /* HAVE_SYS_UN_H */ #include #include #include #include #include #include #include "admin.h" /** achat_t * Internal structure for user by admin threads. */ typedef struct achat_t { int sok; chat_t *chat; user_t *user; pqueue_t *msgqueue; acontext_t *context; char *sokbuf; size_t sokpos; } achat_t; /********************** * Exported Functions * **********************/ static void *admin_thread(void *arg); // pre-declaration /** admin_begin * Initialises an achat_t structure, adds the chat to the global list * of admin sessions and starts an admin thread associated with it. */ void admin_begin(int sok, chat_t *chat) { achat_t *ac = xalloc(sizeof(achat_t)); ac->sok = sok; ac->context = admin_new_context(&chat->msgqueue);; ac->msgqueue = &chat->msgqueue; ac->chat = chat; ac->sokbuf = xalloc(1024); ac->sokpos = 0; LOCK(global); LOCK(chat); PTRARR_ADD(&global->admins,&global->no_admins,chat); UNLOCK(global); ac->user = chat->admin.user; ac->context->colour = chat->colour; thread_create(&(chat->admin.thread),admin_thread,ac); UNLOCK(chat); } /** admin_open * Initialises an chat_t structure. * If a socket is not already establisted it attempts to add * the session to a CHAT type interface. * If a socket already exists or no CHAT interface is availible * then admin_begin is called to start the associated thread. * User Logon count is checked here, if exceeded then a thread * won't be started, this prevents a denial of service. */ void admin_open(int sok, network_t *net, const char *host, const char *nick, user_t *user) { char buffer[512]; chat_t *chat; int i,tmp; if(sok == -1 && (net == NULL || nick == NULL)) return; if(user != NULL) { LOCK(user); tmp = user->no_permitted_logons; xstrncpy(buffer,user->username,sizeof(buffer)); UNLOCK(user); if(tmp != 0) { LOCK(global); for(i = 0; i < global->no_admins && tmp > 0; ++i) { LOCK(global->admins[i]); if(global->admins[i]->state != STATE_COMPLETE && global->admins[i]->state != STATE_DELETED && global->admins[i]->admin.user == user) tmp--; UNLOCK(global->admins[i]); } UNLOCK(global); if(tmp <= 0) { if(sok != -1) { //D("close: %d",sok); close(sok); } if(net != NULL && nick != NULL) irc_send_notice(net,nick,"XDCC ADMIN Denied: You are at your logon count limit."); LOGP(L_XDC,"XDCC ADMIN for %s Denied: Logon limit reached.",buffer); return; } } } chat = alloc_chat(CHAT_ADMIN); chat->state = STATE_QUEUED; chat->network = net; chat->host = host != NULL ? xstrdup(host) : NULL; chat->nick = nick != NULL ? xstrdup(nick) : NULL; chat->admin.user = user; if(sok == -1) { /* find a suitable chat interface */ LOCK(global); for(i = 0, tmp = 0; i < global->no_interfaces && !tmp; ++i) { LOCK(global->interfaces[i]); if(global->interfaces[i]->type == INTERFACE_CHAT && global->interfaces[i]->def == 1) { PTRARR_ADD(&global->interfaces[i]->chat.chats, &global->interfaces[i]->chat.no_chats, chat); tmp = 1; } UNLOCK(global->interfaces[i]); } UNLOCK(global); if(tmp == 0) { LOCK(net); LOGP(L_ERR,"Unable to find suitable chat interface for XDCC ADMIN to [%s:%s] will issue DCC CHAT from thread.", net->name,nick); UNLOCK(net); } else return; } admin_begin(sok,chat); } /** admin_purge * Frees deleted chat structures, thread joining is also done. */ void admin_purge(const time_t now) { chat_t **old = NULL; int i,j,deleted,no_old = 0; //D("Admin Purge Begins"); LOCK(global); for(i = 0, deleted = 0; i < global->no_admins; ++i) { LOCK(global->admins[i]); if(global->admins[i]->state == STATE_COMPLETE) deleted++; } if(deleted) { old = global->admins; no_old = global->no_admins; PTRARR_ALLOC(&(global->admins),&(global->no_admins), no_old - deleted); for(i = 0,j = 0; i < no_old; ++i) { if(old[i]->state != STATE_COMPLETE) { UNLOCK(old[i]); global->admins[j++] = old[i]; old[i] = NULL; } } } else { for(i = 0; i < global->no_admins; ++i) UNLOCK(global->admins[i]); } UNLOCK(global); if(old != NULL) { for(i = 0; i < no_old; ++i) { if(old[i] != NULL) { UNLOCK(old[i]); thread_end(&(old[i]->admin.thread)); thread_join(&(old[i]->admin.thread),NULL); free_chat(old[i]); } } xfree(old); } //D("Admin Purge Ends"); } int admin_valid(chat_t *ac) { int i; if(ac == NULL) return 0; LOCK(global); for(i = 0; i < global->no_admins; ++i) { if(global->admins[i] == ac) { LOCK(ac); UNLOCK(global); if(ac->state != STATE_COMPLETE) { return 1; } else { UNLOCK(ac); return 0; } } } UNLOCK(global); return 0; } /** admin_nick_change * Is called to signal a nick name change on an IRC network, it updates * any references in the admin structures from net:onick to net:nnick. */ void admin_nick_change(network_t *net, const char *onick, const char *nnick) { int i; LOCK(global); for(i = 0; i < global->no_admins; ++i) { chat_t *a = global->admins[i]; LOCK(a); if(a->network == net && irc_strcasecmp(a->nick,onick) == 0) { xfree(a->nick); a->nick = xstrdup(nnick); } UNLOCK(a); } UNLOCK(global); } #ifdef HAVE_SYS_UN_H /** admin_socket_open * Creates a unix listening socket on a path. * Returns -1 on failure, otherwise the new socket. */ int admin_socket_open(const char *path) { struct sockaddr_un addr; struct stat sbuf; int ret,sok; ret = stat(path,&sbuf); if(ret != -1) { if(S_ISSOCK(sbuf.st_mode)) { ret = unlink(path); if(ret == -1) return -1; } else return -1; } ret = socket(PF_UNIX,SOCK_STREAM,0); if(ret == -1) return -1; else sok = ret; addr.sun_family = AF_UNIX; xstrncpy(addr.sun_path,path,UNIX_PATH_MAX); ret = bind(sok,(struct sockaddr *)&addr,sizeof(addr)); if(ret == -1) { //D("close: %d",sok); close(sok); return -1; } ret = fcntl(sok,F_SETFL,O_NONBLOCK); if(ret == -1) { //D("close: %d",sok); close(sok); return -1; } listen(sok,5); return sok; } /** admin_socket_poll * Checks an admin socket for new connections, calls admin_begin on any * that occur. Will wait sec + usec, however will return immediately if * any new connection are made. Returns -1 on error, 0 on success. */ int admin_socket_poll(int sok, long sec, long usec) { struct pollfd pfd; int ret; pfd.fd = sok; pfd.events = POLLIN; pfd.revents = 0; ret = poll(&pfd,1,(sec * 1000) + (usec / 1000)); if(ret < 1) { if(ret == -1) E(errno); if(ret == -1 && errno == EINTR) ret = 0; return ret; } else if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) return -1; do { ret = accept(sok,NULL,NULL); if(ret != -1) admin_begin(ret,alloc_chat(CHAT_ADMIN)); } while(ret != -1); return 0; } /** admin_socket_close * shuts down an admin socket and deletes the associated * path if the path is of type socket. */ void admin_socket_close(const char *path, int sok) { struct stat sbuf; int ret; //D("close: %d",sok); close(sok); if(path != NULL) { ret = stat(path,&sbuf); if(ret != -1) { if(S_ISSOCK(sbuf.st_mode)) unlink(path); } } } #endif /* HAVE_SYS_UN_H */ /******************************* * Admin Chat Thread Functions * *******************************/ /** has_pending_data * Returns 1 is socket s has pending data, -1 if an error occured else 0. */ static int has_pending_data(int s) { struct pollfd pfd; int ret; //D(""); pfd.fd = s; pfd.events = POLLIN; ret = poll(&pfd,1,0); if(ret == -1) { E(errno); return -1; } if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) return -1; else if(pfd.revents & POLLIN) return 1; else return 0; } /** recv_line * Attempts to receive a m/[\r]?\n/ terminated line from the socket of an * admin session, excess data is stored in the sokbuf associated with the * session and resumed on subsequent calls. * Returns -1 on error, 0 on no message, otherwise message length. */ static ssize_t recv_line(achat_t *ac, char *buffer, size_t bufsize) { ssize_t ret; int i; if(ac->sokpos != 0) { if(bufsize < ac->sokpos+1) return -1; else strncpy(buffer,ac->sokbuf,ac->sokpos); } do { ret = recv(ac->sok,buffer + ac->sokpos, bufsize - ac->sokpos,MSG_PEEK); if(ret < 0 && errno != EINTR) { E(errno); return -1; } } while(ret == -1 && errno == EINTR); for(i = 0; i < (ret+ac->sokpos); ++i) { if(buffer[i] == '\n') { do { ret = recv(ac->sok,buffer + ac->sokpos, (i+1) - ac->sokpos,0); if(ret < 0 && errno != EINTR) { E(errno); return -1; } } while(ret == -1 && errno == EINTR); buffer[i] = '\0'; if(i != 0) if(buffer[i-1] == '\r') buffer[i-1] = '\0'; ac->sokpos = 0; return strlen(buffer)+1; } } do { ret = recv(ac->sok,ac->sokbuf + ac->sokpos,ret,0); if(ret < 0 && errno != EINTR) { E(errno); return -1; } } while(ret == -1 && errno == EINTR); ac->sokpos += ret; if(ac->sokpos >= 512) ac->sokpos = 0; // clear buffer on oversize message E(errno); errno = EAGAIN; if(ret > 0) return 0; else return -1; } static int wait_for_data(achat_t *ac, int timeout) { int i; for(i = 0; i < (timeout*2); ++i) { switch (has_pending_data(ac->sok)) { case -1: return -1; case 1: return (i/2); default: usleep(500000); } } return -1; } /** initiate_chat * Creates a listening socket and sends a CTCP DCC CHAT message over IRC, * then waits 180s for a connection. If successful the resulting socket is * assigned to the achat_t structure and 0 returned otherwise -1. * This function is used when no CHAT interfaces are availible. */ static int initiate_chat(achat_t *ac) { char buffer[64],nick[32]; struct pollfd pfd; struct sockaddr_in addr; socklen_t len; network_t *net; int i,ret; ac->sok = socket(PF_INET,SOCK_STREAM,0); if(ac->sok == -1) return -1; addr.sin_family = AF_INET; addr.sin_port = 0; addr.sin_addr.s_addr = INADDR_ANY; ret = bind(ac->sok,(struct sockaddr *)&addr,sizeof(addr)); if(ret == -1) { //D("close: %d",ac->sok); close(ac->sok); return -1; } len = sizeof(addr); getsockname(ac->sok,(struct sockaddr *)&addr,&len); listen(ac->sok,1); LOCK(ac->chat); ac->chat->state = STATE_LISTENING; net = ac->chat->network; if(ac->chat->nick != NULL) xstrncpy(nick,ac->chat->nick,sizeof(nick)); else nick[0] = '\0'; UNLOCK(ac->chat); if(net == NULL) { //D("close: %d",ac->sok); close(ac->sok); errno = ENETUNREACH; return -1; } snprintf(buffer,sizeof(buffer), "DCC CHAT CHAT $IIP %u", ntohs(addr.sin_port)); irc_send_ctcp_msg(net,nick,buffer); LOCK(net); LOGTP(L_INF,"Dispatch DCC CHAT message to [%s:%s]", net->name,nick); UNLOCK(net); pfd.fd = ac->sok; pfd.events = POLLIN | POLLOUT; pfd.revents = 0; for(i = 0; i < 180; ++i) { ret = poll(&pfd,1,1000); if(ret != 0) { break; } else if(thread_should_end()) { errno = EINTR; ret = -1; break; } } if(ret < 1 || (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))) { i = errno; //D("close: %d",ac->sok); close(ac->sok); errno = i; if(ret == 0) errno = ETIMEDOUT; return -1; } ret = accept(ac->sok,NULL,NULL); if(ret == -1) { i = errno; //D("close: %d",ac->sok); close(ac->sok); errno = i; return -1; } //D("close: %d",ac->sok); close(ac->sok); ac->sok = ret; return 0; } /** authenticate_chat * Authenticates, the user against their their password. * Determines the remote user if not already know. * Checks that the remote hostname matches one of the users permitted * hostmasks. Hardcoded retry limits 1 user retry, 1 password retry. * Returns -1 on authentication failure and socket errors, 0 on success. */ static int authenticate_chat(achat_t *ac) { char buffer[512]; struct sockaddr_in saddr; struct sockaddr_in *addr; socklen_t len; int i,j,ret; #ifdef HAVE_SYS_UN_H len = IMAX(sizeof(struct sockaddr_in),sizeof(struct sockaddr_un)); #else len = sizeof(struct sockaddr_in); #endif addr = xalloc(len); ret = getsockname(ac->sok,(struct sockaddr *)addr,&len); if(addr->sin_family == AF_INET) { len = sizeof(struct sockaddr_in); ret = getpeername(ac->sok,(struct sockaddr *)addr,&len); if(ret == -1) { LOGTP(L_ERR,"Failed to get peer name"); xfree(addr); return -1; } memcpy(&saddr,addr,sizeof(struct sockaddr_in)); inet_ntop(AF_INET,&addr->sin_addr,buffer,sizeof(buffer)); LOGTP(L_INF,"Connected to (%s:%u)",buffer,ntohs(addr->sin_port)); #ifndef HAVE_SYS_UN_H } #else } else { LOGTP(L_INF,"Connected via Admin Socket"); saddr.sin_family = AF_UNIX; } #endif /* HAVE_SYS_UN_H */ xfree(addr); if(ac->user == NULL) { for(j = 0; j < 2; ++j) { i = snprintf(buffer,sizeof(buffer),"Username:\r\n"); if((ret = send(ac->sok,buffer,i,0)) == -1) return ret; if((ret = wait_for_data(ac,180)) == -1) return ret; do { if((ret = recv_line(ac,buffer,sizeof(buffer))) == -1) return ret; } while(ret == 0); //D("buffer: %s",buffer); ac->user = user_find(buffer); if(ac->user != NULL) break; } if(j >= 2) { LOGTP(L_ERR,"Authentication Failure: Retry Limit Exceeded (Username)"); i = snprintf(buffer,sizeof(buffer),"Retry Limit Exceeded\r\n"); send(ac->sok,buffer,i,0); return -1; } } LOCK(ac->user); LOGTP(L_INF,"Username: %s",ac->user->username); if(ac->user->no_permitted_logons != 0) { j = ac->user->no_permitted_logons; UNLOCK(ac->user); LOCK(global); for(i = 0; i < global->no_admins && j > 0; ++i) { LOCK(global->admins[i]); if(global->admins[i] != ac->chat && global->admins[i]->state != STATE_COMPLETE && global->admins[i]->state != STATE_DELETED && global->admins[i]->admin.user == ac->user) j--; UNLOCK(global->admins[i]); } UNLOCK(global); if(j <= 0) { LOGTP(L_ERR,"Authentication Failure: Logon count exceeded"); i = snprintf(buffer,sizeof(buffer), "Logon count exceeded\r\n"); send(ac->sok,buffer,i,0); return -1; } } else UNLOCK(ac->user); if(saddr.sin_family == AF_INET) { char **names; int no_names; ret = aresolv_addrtonames(global->aresolver,AF_INET, &(saddr.sin_addr),&names); //D("host: %s",buffer); if(ret < 1) { char addrbuf[64]; LOGTP(L_ERR,"Unable to determine remote hostname, using IP to test access list"); inet_ntop(AF_INET,&(saddr.sin_addr), addrbuf,sizeof(addrbuf)); names = xalloc(sizeof(char *) + strlen(addrbuf) + 1); names[0] = (char *)&(names[1]); strcpy(names[0], addrbuf); no_names = 1; } else { no_names = ret; } LOCK(ac->user); for(i = 0, ret = 0; i < ac->user->no_hosts && !ret; ++i) { for(j = 0; j < no_names; ++j) { if(irc_hostmask_match(names[j],ac->user->hosts[i])) ret = 1; } } UNLOCK(ac->user); xfree(names); if(ret == 0) { LOGTP(L_ERR,"Authentication Failure: Host not authorized"); i = snprintf(buffer,sizeof(buffer), "Your host is not authorized\r\n"); send(ac->sok,buffer,i,0); return -1; } } else { /* we don't need to #ifdef this part */ LOCK(ac->user); if(!PRIV_ISSET(ac->user,PRIV_SOCKET)) { UNLOCK(ac->user); LOGTP(L_ERR,"Authentication Failure: User not granted Admin Socket privilege"); i = snprintf(buffer,sizeof(buffer), "You are not authorized to use this socket\r\n"); send(ac->sok,buffer,i,0); return -1; } else UNLOCK(ac->user); } for(i = 0; i < 2; ++i) { j = snprintf(buffer,sizeof(buffer),"Password:\r\n"); if((ret = send(ac->sok,buffer,j,0)) == -1) return ret; if((ret = wait_for_data(ac,180)) == -1) return ret; do { if((ret = recv_line(ac,buffer,sizeof(buffer))) == -1) return ret; } while(ret == 0); //D("ret: %d, buffer: \"%s\"",ret,buffer); if(user_check_password(ac->user,buffer)) { memset(buffer,0,sizeof(buffer)); break; } else memset(buffer,0,sizeof(buffer)); } if(i >= 2) { LOGTP(L_ERR,"Authentication Failure: Retry Limit Exceeded (Password)"); i = snprintf(buffer,sizeof(buffer),"Retry Limit Exceeded\r\n"); send(ac->sok,buffer,i,0); return -1; } LOCK(ac->user); ac->user->last_logon = xtime(); LOGTP(L_INF,"Successfully Authenticated Username: %s",ac->user->username); LOG_TITLE("Admin %s",ac->user->username); UNLOCK(ac->user); LOCK(ac->chat); ac->chat->admin.user = ac->user; UNLOCK(ac->chat); ac->context->user = ac->user; se_notify_admin_logon(ac->chat,ac->user); return 0; } /** admin_send_msgs * Sends out all the message queue, returns -1 if a socket error occurs * otherwise 0. Messace colour coding is done here. */ static int admin_send_msgs(achat_t *ac) { char cbuffer[510],buffer[514],*ptr; int len,ret; while((ptr = pqueue_pop_front(ac->msgqueue)) != NULL) { //LOG("Admin Thread (%p), Sending: %s",ac,ptr); colourise(ac->context->colour,cbuffer,sizeof(cbuffer),ptr); xfree(ptr); len = IMIN( snprintf(buffer,sizeof(buffer)-1,"%s\r\n",cbuffer), sizeof(buffer)-2 ); ret = send(ac->sok,buffer,len,0); if(ret < len) { //D("ret: %d, len: %d",ret,len); return -1; } } return 0; } /** admin_recv_msgs * Recieves messages one at a time using recv_line, calls process_cmd on * them, until there are no more pending messages. * Returns -1 on socket error else 0. */ static int admin_recv_msgs(achat_t *ac) { char buffer[512]; int ret; do { ret = recv_line(ac,buffer,sizeof(buffer)); if(ret > 0) { LOCK(ac->chat); ac->chat->last_contact = xtime(); UNLOCK(ac->chat); //LOG("Admin Thread (%p), Received: %s\n",ac,buffer); admin_process_cmd(ac->context,buffer,sizeof(buffer)); memset(buffer,0,sizeof(buffer)); } else if(ret < 0) { E(errno); break; } } while(has_pending_data(ac->sok) > 0); return ret; } /** admin_thread * Main loop used by any admin session. Calls other functions to initiate * and authenticate the admin session. * Uses a blocking socket, and poll call with a 15 second time out, * hence the inner loop will generally iterate once every 15 seconds. * On each iteration, the state of the session is check to make sure it * hasn't been closed, and that the user hasn't been deleted. * Every 60 seconds the admin_check function is also called. * On exiting the loop all deinitialization is done except for * de-allocation of the chat_t structure, which is left to the admin_purge * function so that thread joining can take place. */ static void *admin_thread(void *arg) { #define SIG 1 #define SOK 0 achat_t *ac = (achat_t *)arg; struct pollfd pfd[2]; int signal_fd[2] = {-1,-1}; int ret,i; time_t now,last_check; thread_signal_started(&(ac->chat->admin.thread)); LOG_TITLE("Admin (%p)",ac->chat); LOCK(ac->chat); ac->chat->started = xtime(); if(ac->sok == -1 && (ac->chat->network == NULL || ac->chat->nick == NULL)) { UNLOCK(ac->chat); goto out; } UNLOCK(ac->chat); if(ac->sok == -1) { ret = initiate_chat(ac); if(ret == -1) { char errbuf[96]; LOGTP(L_ERR,"Error Initiating Chat: %s", lstrerror_r(errno,errbuf,sizeof(errbuf))); goto out; } } /* Make sure the socket IS blocking, socket which come from the * interface module will be set nonblocking. */ ret = fcntl(ac->sok,F_GETFL); if(ret & O_NONBLOCK) { ret = ret & ~O_NONBLOCK; fcntl(ac->sok,F_SETFL,ret); } ret = authenticate_chat(ac); if(ret == -1) goto out; else { i = 1; setsockopt(ac->sok,SOL_SOCKET,SO_KEEPALIVE,&i,sizeof(i)); } LOCK(ac->chat); ac->chat->state = STATE_ACTIVE; ac->chat->connected = xtime(); ac->chat->last_contact = xtime(); UNLOCK(ac->chat); pfd[SOK].fd = ac->sok; pfd[SOK].events = POLLIN; pfd[SIG].fd = -1; pfd[SIG].events = POLLIN; ret = pipe(signal_fd); if(ret != 0) { LOGTP(L_ERR,"Failed to allocate signaling pipe"); signal_fd[0] = -1; } else { fcntl(signal_fd[0],F_SETFL,O_NONBLOCK); fcntl(signal_fd[1],F_SETFL,O_NONBLOCK); pqueue_set_notification_fd(ac->msgqueue,signal_fd[1]); pfd[SIG].fd = signal_fd[0]; } admin_output_prompt(ac->context); for(last_check = xtime();;) { now = xtime(); LOCK(ac->chat); if(ac->chat->state == STATE_DELETED || ac->chat->state == STATE_COMPLETE) { UNLOCK(ac->chat); break; } UNLOCK(ac->chat); LOCK(ac->user); if(ac->user->deleted != 0) { LOGTP(L_ERR,"User: %s was deleted",ac->user->username); UNLOCK(ac->user); break; } UNLOCK(ac->user); if((now - last_check) > 60) { admin_context_check(ac->context,now); last_check = now; } if(admin_context_complete(ac->context)) break; if(pqueue_length(ac->msgqueue) == 0) { pfd[SIG].revents = 0; pfd[SOK].revents = 0; ret = poll(pfd,signal_fd[0] == -1 ? 1 : 2, 15*1000); if(ret > 0) { if(pfd[SOK].revents & (POLLERR | POLLHUP | POLLNVAL)) ret = -1; else if(pfd[SOK].revents & POLLIN) ret = admin_recv_msgs(ac); else if(pfd[SIG].revents & POLLIN) { do { char buffer[4]; ret = read(signal_fd[0],buffer,1); } while(ret > 0); ret = 0; } } } else ret = admin_send_msgs(ac); if(ret == -1) { char errbuf[96]; if(errno == 0) { socklen_t len; int val; len = sizeof(val); ret = getsockopt(ac->sok,SOL_SOCKET,SO_ERROR,&val,&len); errno = ret != -1 ? val : errno; } LOGTP(L_ERR,"Error: %s", lstrerror_r(errno,errbuf,sizeof(errbuf))); break; } } out: log_unsubscribe(ac->msgqueue); irc_unlisten(NULL,NULL,ac->msgqueue); admin_party_line_unsubscribe(ac->context); if(ac->context->user != NULL) se_notify_admin_logoff(ac->context->user); admin_free_context(ac->context); pqueue_clear(ac->msgqueue,xfree,1); if(signal_fd[0] != -1) { //D("close: %d",signal_fd[0]); close(signal_fd[0]); } if(ac->sok != -1) { send(ac->sok,"Ja ne!\r\n",8,0); //D("close: %d",ac->sok); close(ac->sok); } LOCK(ac->chat); ac->chat->state = STATE_COMPLETE; UNLOCK(ac->chat); LOGTP(L_INF,"Shutdown"); xfree(ac->sokbuf); xfree(ac); thread_signal_finished(); return NULL; #undef SIG #undef SOK }