/* EnergyMech, IRC bot software Parts Copyright (c) 1997-2018 proton 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef GENCMD_C #define SOCKET_C #include "config.h" #include "defines.h" #include "structs.h" #include "global.h" #include "h.h" #ifndef RAWDNS /* * only include this hack if rawdns isnt compiled in */ uint32_t get_ip(const char *host) { struct hostent *he; uint32_t ip; if ((ip = inet_addr(host)) == INADDR_NONE) { if ((he = gethostbyname(host)) == NULL) { #ifdef DEBUG debug("(get_ip) unable to resolve %s\n",host); #endif /* DEBUG */ return(-1); } ip = (uint32_t)((struct in_addr*)he->h_addr)->s_addr; } #ifdef DEBUG debug("(get_ip) %s -> %s\n",host,inet_ntoa(*((struct in_addr*)&ip))); #endif /* DEBUG */ return(ip); } #endif /* ! RAWDNS */ /* * some default code for socket flags */ void SockFlags(int fd) { #ifdef ASSUME_SOCKOPTS fcntl(fd,F_SETFL,O_NONBLOCK|O_RDWR); fcntl(fd,F_SETFD,FD_CLOEXEC); #else /* not ASSUME_SOCKOPTS */ fcntl(fd,F_SETFL,O_NONBLOCK | fcntl(fd,F_GETFL)); fcntl(fd,F_SETFD,FD_CLOEXEC | fcntl(fd,F_GETFD)); #endif /* ASSUME_SOCKOPTS */ } int SockOpts(void) { struct { int onoff; int linger; } parm; int s; if ((s = socket(AF_INET,SOCK_STREAM,0)) < 0) return(-1); parm.onoff = parm.linger = 0; setsockopt(s,SOL_SOCKET,SO_LINGER,(char*)&parm,sizeof(parm)); parm.onoff++; setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(char*)&parm.onoff,sizeof(int)); SockFlags(s); return(s); } int SockListener(int port) { struct sockaddr_in sai; int s; if ((s = SockOpts()) < 0) return(-1); memset((char*)&sai,0,sizeof(sai)); sai.sin_family = AF_INET; sai.sin_addr.s_addr = INADDR_ANY; sai.sin_port = htons(port); if ((bind(s,(struct sockaddr*)&sai,sizeof(sai)) < 0) || (listen(s,1) < 0)) { close(s); return(-1); } return(s); } #ifdef RAWDNS #define get_ip rawdns_get_ip #endif /* RAWDNS */ int SockConnect(char *host, int port, int use_vhost) { struct sockaddr_in sai; int s; #ifdef DEBUG debug("(SockConnect) %s %i%s\n",nullstr(host),port,(use_vhost) ? " [VHOST]" : ""); #endif /* DEBUG */ if ((s = SockOpts()) < 0) return(-1); memset((char*)&sai,0,sizeof(sai)); sai.sin_family = AF_INET; if (use_vhost && ((current->vhost_type & VH_IPALIAS_FAIL) == 0) && current->setting[STR_VIRTUAL].str_var) { current->vhost_type |= VH_IPALIAS_BOTH; if ((sai.sin_addr.s_addr = get_ip(current->setting[STR_VIRTUAL].str_var)) != -1) { if (bind(s,(struct sockaddr *)&sai,sizeof(sai)) >= 0) { current->vhost_type &= ~(VH_IPALIAS_FAIL|VH_WINGATE); #ifdef WINGATE use_vhost++; #endif /* WINGATE */ #ifdef DEBUG debug("(SockConnect) IP Alias virtual host bound OK\n"); #endif /* DEBUG */ } } } memset((char*)&sai,0,sizeof(sai)); sai.sin_family = AF_INET; #ifdef WINGATE /* * bouncer connects (WinGate, ...) */ if ((use_vhost == TRUE) && ((current->vhost_type & VH_WINGATE_FAIL) == 0) && current->setting[STR_WINGATE].str_var) { #ifdef DEBUG debug("(SockConnect) Trying wingate @ %s:%i\n", current->setting[STR_WINGATE].str_var, current->setting[INT_WINGPORT].int_var); #endif /* DEBUG */ current->vhost_type |= VH_WINGATE_BOTH; sai.sin_port = htons(current->setting[INT_WINGPORT].int_var); if ((sai.sin_addr.s_addr = get_ip(current->setting[STR_WINGATE].str_var)) == -1) { close(s); return(-1); } current->vhost_type &= ~(VH_WINGATE_FAIL|VH_IPALIAS); #ifdef DEBUG debug("(SockConnect) WINGATE host resolved OK\n"); #endif /* DEBUG */ } else #endif /* WINGATE */ { /* * Normal connect, no bounces... */ sai.sin_port = htons(port); if ((sai.sin_addr.s_addr = get_ip(host)) == -1) { close(s); return(-1); } sai.sin_family = AF_INET; } if ((connect(s,(struct sockaddr*)&sai,sizeof(sai)) < 0) && (errno != EINPROGRESS)) { #ifdef DEBUG debug("[CbN] unable to connect. errno = %i\n",errno); #endif /* DEBUG */ close(s); return(-1); } #ifdef DEBUG debug("(SockConnect) {%i} %s %i%s\n",s,nullstr(host),port,(use_vhost) ? " [VHOST]" : ""); #endif /* DEBUG */ return(s); } int SockAccept(int sock) { struct sockaddr_in sai; int s,sz; sz = sizeof(sai); s = accept(sock,(struct sockaddr*)&sai,&sz); if (s >= 0) { SockFlags(s); } return(s); } #endif /* GENCMD_C */ /* * Format text and send to a socket or file descriptor */ int to_file(const int sock, const char *format, ...) { va_list msg; #if defined(DEBUG) && !defined(GENCMD_C) char *line,*rest; int i; #endif /* DEBUG */ if (sock == -1) return(-1); va_start(msg,format); vsprintf(globaldata,format,msg); va_end(msg); #if defined(DEBUG) && !defined(GENCMD_C) i = write(sock,globaldata,strlen(globaldata)); rest = globaldata; while((line = get_token(&rest,"\n"))) /* rest cannot be NULL */ debug("(out) {%i} %s\n",sock,nullstr(line)); if (i < 0) debug("(out) {%i} errno = %i\n",sock,errno); return(i); #else /* DEBUG */ return(write(sock,globaldata,strlen(globaldata))); #endif /* DEBUG */ } #ifndef GENCMD_C /* * Format a message and send it to the current bots server * to_server needs a newline (\n) it wont manufacture it itself. */ void to_server(char *format, ...) { va_list msg; #ifdef DEBUG char *line,*rest; #endif /* DEBUG */ if (current->sock == -1) return; va_start(msg,format); vsprintf(globaldata,format,msg); va_end(msg); /* * each line we send to the server pushes the sendq time * forward *two* seconds */ current->sendq_time += 2; if (write(current->sock,globaldata,strlen(globaldata)) < 0) { #ifdef DEBUG debug("[StS] {%i} errno = %i\n",current->sock,errno); #endif /* DEBUG */ close(current->sock); current->sock = -1; current->connect = CN_NOSOCK; return; } #ifdef DEBUG rest = globaldata; while((line = get_token(&rest,"\n"))) /* rest cannot be NULL */ debug("[StS] {%i} %s\n",current->sock,line); #endif /* DEBUG */ } void to_user_q(const char *target, const char *format, ...) { va_list args; Client *client; Strp *new,**pp; char message[MAXLEN]; char nick[MAXHOSTLEN]; char *fmt; va_start(args,format); vsprintf(message,format,args); va_end(args); stringcat(message,"\n"); if (STARTUP_ECHOTOCONSOLE) { int n; n = write(1,message,strlen(message)); return; } fmt = "NOTICE %s :%s"; if (CurrentChan) { target = CurrentChan->name; fmt = "PRIVMSG %s :%s"; } if (!ischannel(target)) target = nickcpy(nick,target); if ((client = find_client(target))) { if (write(client->sock,message,strlen(message)) < 0) client->flags = DCC_DELETE; #ifdef DEBUG debug("(to_user) {%i} [%s] %s",client->sock,target,message); #endif /* DEBUG */ return; } #ifdef REDIRECT if (redirect.to && target == redirect.to) { send_redirect(message); return; } #endif /* REDIRECT */ pp = ¤t->sendq; while(*pp) pp = &(*pp)->next; set_mallocdoer(to_user_q); *pp = new = (Strp*)Calloc(sizeof(Strp) + StrlenX(fmt,target,message,NULL)); /* Calloc sets to zero new->next = NULL; */ sprintf(new->p,fmt,target,message); } /* * Format a message and send it either through DCC if the user is * connected to the partyline, or send it as a NOTICE if he's not */ void to_user(const char *target, const char *format, ...) { va_list args; Client *client; char message[MAXLEN]; char *s; #ifdef DEBUG if (!*target) return; #endif /* DEBUG */ s = message; #ifdef REDIRECT if (redirect.to) client = NULL; else client = find_client(target); if (!redirect.to && !client) #else /* REDIRECT */ if ((client = find_client(target)) == NULL) #endif /* REDIRECT */ { if (current->sock == -1) { #ifdef DEBUG debug("(to_user) [%s] current->sock == -1\n",target); #endif /* DEBUG */ return; } s = stringcpy(message,"NOTICE "); if (ischannel(target)) stringcat(message,target); else nickcpy(s,target); s = stringcat(message," :"); } va_start(args,format); vsprintf(s,format,args); va_end(args); #ifdef REDIRECT if (redirect.to) { send_redirect(message); return; } #endif /* REDIRECT */ /* * tag on a newline for DCC or server */ s = stringcat(message,"\n"); #ifdef DEBUG *s = 0; #endif /* DEBUG */ if (client) { if (write(client->sock,message,(s - message)) < 0) client->flags = DCC_DELETE; #ifdef DEBUG debug("(to_user) {%i} [%s] %s",client->sock,target,message); #endif /* DEBUG */ return; } current->sendq_time += 2; if (write(current->sock,message,(s - message)) < 0) { #ifdef DEBUG debug("(to_user) {%i} [%s] errno = %i\n",current->sock,target,errno); #endif /* DEBUG */ close(current->sock); current->sock = -1; current->connect = CN_NOSOCK; return; } #ifdef DEBUG debug("(to_user) {%i} [%s] %s",current->sock,target,message); #endif /* DEBUG */ } #endif /* ifndef GENCMD_C */ /* * Read any data waiting on a socket or file descriptor * and return any complete lines to the caller * * 1: Try to get a whole line from data * 2: If data is insufficient, try to read in more * 3: Try again to make a whole line */ char *sockread(int socket, char *rest, char *output) { char *src,*dst,*rdst; int n; errno = EAGAIN; src = rest; dst = output; while(*src) { if (*src == '\n' || *src == '\r') { gotline: while(*src == '\n' || *src == '\r') src++; *dst = 0; #if !defined(GENCMD_C) cx.rest_end = dst; #endif /* !defined(GENCMD_C) */ /* move remainder of rest to the beginning of the buffer */ /* src can be end of rest or globaldata */ dst = rest; while(*src) *(dst++) = *(src++); *dst = 0; #if defined(DEBUG) && !defined(GENCMD_C) debug("(in) {%i} %s\n",socket,output); #endif /* DEBUG */ return((*output) ? output : NULL); } *(dst++) = *(src++); } n = read(socket,globaldata,MSGLEN-2); switch(n) { case 0: errno = EPIPE; case -1: return(NULL); } rdst = src; globaldata[n] = 0; src = globaldata; while(*src) { if (*src == '\r' || *src == '\n') goto gotline; /* gotline will move the rest of globaldata to rest */ if ((dst - output) >= (MSGLEN-2)) { /* * line is longer than buffer, let the wheel spin */ src++; continue; } *(rdst++) = *(dst++) = *(src++); } *rdst = 0; return(NULL); } void readline(int fd, int (*callback)(char *)) { char linebuf[MSGLEN],readbuf[MSGLEN]; char *ptr; int oc; *readbuf = 0; do { ptr = sockread(fd,readbuf,linebuf); oc = errno; if (ptr && *ptr) { if (callback(ptr) == TRUE) oc = EPIPE; } } while(oc == EAGAIN); close(fd); #if defined(DEBUG) && !defined(GENCMD_C) debug("(readline) done reading lines\n"); #endif /* DEBUG */ } #ifndef GENCMD_C void remove_ks(KillSock *ks) { KillSock *ksp; close(ks->sock); if (ks == killsocks) { killsocks = ks->next; } else { ksp = killsocks; while(ksp && (ksp->next != ks)) ksp = ksp->next; if (ksp) { ksp->next = ks->next; } } #ifdef DEBUG debug("(killsock) {%i} removing killsocket\n",ks->sock); #endif /* DEBUG */ Free((char**)&ks); } int killsock(int sock) { KillSock *ks,*ksnext; struct timeval t; fd_set rd,wd; char bitbucket[MSGLEN]; int hi,n; if (sock >= 0) { set_mallocdoer(killsock); ks = (KillSock*)Calloc(sizeof(KillSock)); ks->time = now; ks->sock = sock; ks->next = killsocks; killsocks = ks; #ifdef DEBUG debug("(killsock) {%i} added killsocket\n",ks->sock); #endif /* DEBUG */ return(TRUE); } if (killsocks == NULL) return(FALSE); if (sock == -1) t.tv_sec = 0; else t.tv_sec = 1; t.tv_usec = 0; FD_ZERO(&rd); FD_ZERO(&wd); hi = -1; for(ks=killsocks;ks;ks=ks->next) { if (ks->sock > hi) hi = ks->sock; FD_SET(ks->sock,&rd); FD_SET(ks->sock,&wd); } if (select(hi+1,&rd,&wd,NULL,&t) == -1) { switch(errno) { case EINTR: /* have to redo the select */ case ENOMEM: /* should never happen, but still */ return(TRUE); } } for(ks=killsocks;ks;) { ksnext = ks->next; if (FD_ISSET(ks->sock,&rd)) { n = read(ks->sock,&bitbucket,MSGLEN); if ((n == 0) || ((n == -1) && (errno != EAGAIN))) remove_ks(ks); } if ((now - ks->time) > KILLSOCKTIMEOUT) remove_ks(ks); ks = ksnext; } return(TRUE); } void do_clearqueue(COMMAND_ARGS) { #ifdef DEBUG debug("(do_clearqueue) purging sendq...\n"); #endif purge_linklist((void**)¤t->sendq); } #endif /* GENCMD_C */