#include "ircbouncer.h" char version[] = "1.1.24"; char *logfile = LOGFILE; int ignorenextnick; char sname[] = SNAME; /* id du serveur/bnc */ char bname[] = BNAME; /* nom du bnc */ char identname[] = IDENTNAME; char identhost[] = IDENTHOST; char curnick[NICKLEN] = FIRSTNICK; int listenedsocket = -1; int newclient = 0; int masterrunning = 0; int iorunning = 0; int logsize_max = LOGSIZE_MAX; int logsize_min = LOGSIZE_MIN; char *uptime; /* le serveur irc par défaut */ char ircserverhost[PHLEN] = DEFIRCSERV; int ircserverport = DEFIRCSERVPORT; extern struct endstruct clientstruct, serverstruct; struct chanlog_list prvlog; /* {{{ timestamp */ char * timestamp(void) { /* on peut mettre le buffer en statique tout en restant threadsafe, puisque * si deux threads appellent en meme temps, ils auront la meme heure :) */ static char ts[PHLEN]; time_t tv; struct tm *tm; time(&tv); tm = gmtime(&tv); snprintf(ts, PHLEN, "%.2d-%.2d:%.2d:%.2d", tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return ts; } /* }}} */ /* {{{ log */ /* maintient le fichier de log dans des proportions acceptables */ FILE * openlogchksz() { struct stat logstat; FILE *logfd, *oldlogfd; char c = 0; long l; /* on regarde la taille du fichier de log */ if (stat(logfile, &logstat)) return fopen(logfile, "a+"); /* si elle est assez petite on l'ouvre comme ca */ if (logstat.st_size <= logsize_max) return fopen(logfile, "a"); /* sinon on recopie la fin au début */ if (!(oldlogfd = fopen(logfile, "r"))) return oldlogfd; fseek(oldlogfd, -logsize_min, SEEK_END); /* on cherche la fin de la ligne en cours */ while ((c != '\n') && (fread(&c, 1, 1, oldlogfd) == 1)) ; /* on reouvre le fichier de log pour écrire */ if (!(logfd = fopen(logfile, "r+"))) return NULL; /* et on translate jusqu'à la fin 4 par 4 octets */ while (fread(&l, 4, 1, oldlogfd) > 0) fwrite(&l, 4, 1, logfd); /* on ferme l'ancienne copie et on coupe la nouvelle qu'on renvoie */ fclose(oldlogfd); ftruncate(fileno(logfd), ftell(logfd)); return logfd; } /* enregistre la phrase dans le fichier de log */ void log(char *fmt, ...) { va_list ap; FILE *logfd; va_start(ap, fmt); if (!(logfd = openlogchksz())) return; fprintf(logfd, "%s : ", timestamp()); vfprintf(logfd, fmt, ap); fprintf(logfd, "\n"); fclose(logfd); va_end(ap); } /* }}} */ /* {{{ readphrase */ /* renvoie un buffer contenant la derniere phrase lue depuis le parametre */ char * readphrase(struct endstruct *lpes) { char *phrase; struct liste_phrases *lplp = lpes->i_phrase_tete; if ((lpes->socketdesc < 0) || !lplp) return NULL; pthread_mutex_lock(&lpes->i_phrase_mutex); /* on retire la phrase de la liste */ lpes->i_phrase_tete = lplp->suivante; if (lplp->suivante) lplp->suivante->precedente = NULL; else lpes->i_phrase_queue = NULL; pthread_mutex_unlock(&lpes->i_phrase_mutex); phrase = lplp->phrase; free(lplp); return phrase; } /* }}} */ /* {{{ writephrase */ /* ecrit la phrase dans le buffer de sortie du parametre */ void writephrase(struct endstruct *lpes, char *phrase) { struct liste_phrases *lplp = (struct liste_phrases *) malloc(sizeof (struct liste_phrases)); if (!phrase || (lpes->socketdesc < 0)) { free(lplp); return; } if (!lplp || !(lplp->phrase = strdup(phrase))) { log("writephrase : malloc : %s", strerror(errno)); free(lplp); return; } /* on insere la phrase dans la liste */ lplp->precedente = NULL; pthread_mutex_lock(&lpes->o_phrase_mutex); lplp->suivante = lpes->o_phrase_tete; lpes->o_phrase_tete = lplp; if (lplp->suivante) lplp->suivante->precedente = lplp; else lpes->o_phrase_queue = lplp; pthread_mutex_unlock(&lpes->o_phrase_mutex); } /* }}} */ /* {{{ sendphrase */ void sendphrase(struct endstruct *lpes, char *fmt, ...) { va_list ap; char buff[PHLEN]; va_start(ap, fmt); vsnprintf(buff, PHLEN, fmt, ap); writephrase(lpes, buff); va_end(ap); } /* }}} */ /* {{{ * listen_socket */ /* renvoie le descripteur d'une socket qui écoute sur le port donné, nbloq */ int listen_socket(int listen_port) { struct sockaddr_in a; int s; int yes; /* créée une socket */ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Error : socket : %s\n", strerror(errno)); return -1; } yes = 1; /* autorise plusieurs connections séquentielles sur la meme socket */ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) < 0) { fprintf(stderr, "Error : setsockopt() : %s\n", strerror(errno)); close(s); return -1; } /* rend la socket non bloquante */ if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) fprintf(stderr, "Warning : ne peut mettre la socket nonblock : %s\n", strerror(errno)); /* binde la socket au port choisi */ memset(&a, 0, sizeof (a)); a.sin_port = htons(listen_port); a.sin_family = AF_INET; if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) { fprintf(stderr, "Error : bind() : %s\n", strerror(errno)); close(s); return -1; } /* ecoute le port */ listen(s, 10); return s; } /* }}} */ /* {{{ * connect_socket[_byname] */ /* renvoie le descripteur d'une socket connectée a l'hote sur le port donnés */ static int connect_socket(struct in_addr ia, int port) { struct sockaddr_in a; int s; /* créée une socket */ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { log("Warning : socket() : %s", strerror(errno)); return -1; } memset(&a, 0, sizeof (a)); a.sin_port = htons(port); a.sin_family = AF_INET; memcpy(&a.sin_addr.s_addr, &ia, sizeof (ia)); /* connecte la socket */ if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) { log("Warning : connect() : %s", strerror(errno)); shutdown(s, SHUT_RDWR); close(s); return -1; } return s; } int connect_socket_byname(char *hostname, int hostport) { struct hostent *phe; struct in_addr ia; if (!(phe = gethostbyname(hostname))) { log("Impossible de résoudre le nom %s : %s", hostname, hstrerror(h_errno)); return -1; } memcpy(&ia, phe->h_addr_list[0], sizeof (ia)); return connect_socket(ia, hostport); } /* }}} */ /* {{{ playalllog */ /* envoie tous le log au client */ void * do_playalllog(void *dummy) { register int i, j; struct chanlog_list *cl = &prvlog; if (cl->log[0]) /* si le log prv existe, informe le client qu'il joint le chan #prv */ sendphrase(&clientstruct, ":%s JOIN :%s\r\n", curnick, cl->name); while (cl) { j = cl->lognextfree; if (!cl->log[j]) /* le log n'a pas encore bouclé */ j = 0; else sendphrase(&clientstruct, ":BncInfo PRIVMSG %s : *Non complet* \r\n", cl->name); /* on envoie chaque phrase du log */ for (i = 0; i < LOGLEN; i++) { if (!cl->log[j]) /* fin du log */ break; writephrase(&clientstruct, cl->log[j]); j = LOGNEXT(j); usleep(2000); } /* et on continue avec le log suivant */ cl = cl->suivant; } log("fin de playalllog"); sendphrase(&clientstruct, ":%s NOTICE %s :fin du playalllog\r\n", sname, curnick); /* fin du thread */ return dummy; /* pour éviter les unused parameter :) */ } /* la fonction d'enveloppe qui lance la précédente dans un thread séparé */ void playalllog(void) { pthread_t logtid; pthread_attr_t logta; pthread_attr_init(&logta); pthread_attr_setdetachstate(&logta, PTHREAD_CREATE_DETACHED); /* cree un thread détaché dont le but est de lire le log au client */ pthread_create(&logtid, &logta, do_playalllog, NULL); pthread_attr_destroy(&logta); } /* }}} */ /* {{{ clearchanlog */ /* vide le contenu du chan */ void clearchanlog(char *chan) { register int i; register struct chanlog_list *cl = &prvlog; /* trouve le log (#prv == prvmsg) */ do { if (!strncasecmp(chan, cl->name, PHLEN)) { for (i = 0; i < LOGLEN; i++) { if (cl->log[i] == NULL) break; free(cl->log[i]); cl->log[i] = NULL; } cl->lognextfree = 0; cl->loglastsend = 0; log(" log de %s cleared", chan); sendphrase(&clientstruct, ":%s PRIVMSG %s :log effacé\r\n", bname, chan); } } while ((cl = cl->suivant)); } /* }}} */ /* {{{ clearlog */ /* efface le contenu du log, mais garde les chans */ void clearlog(void) { register int i; register struct chanlog_list *cl = &prvlog; /* on le contenu de tous les logs a partir de prvlog */ while (cl) { for (i = 0; i < LOGLEN; i++) { if (cl->log[i] == NULL) break; free(cl->log[i]); cl->log[i] = NULL; } cl->lognextfree = 0; cl->loglastsend = 0; cl = cl->suivant; } log(" log vidé"); sendphrase(&clientstruct, ":%s NOTICE %s :log effacé\r\n", sname, curnick); } /* }}} */ /* {{{ dellog */ /* efface completement le log, efface les chans */ void dellog(void) { register struct chanlog_list *cl = &prvlog, *clt; clearlog(); while (cl) { clt = cl->suivant; /* on évite de free(&prvlog) */ if (cl != &prvlog) free(cl); else cl->suivant = NULL; cl = clt; } } /* }}} */ /* {{{ clientjoins */ /* appelé quand le client rejoin un chan * alloue un nouveau log si le chan est un nouveau chan */ void clientjoins(char *chan) { register struct chanlog_list *cl = &prvlog; register int i; while (cl->suivant) { if (!strncasecmp(cl->suivant->name, chan, 32)) /* le chan a déjà une entrée dans la liste */ return; cl = cl->suivant; } /* sinon ajout en fin de liste */ cl = cl->suivant = (struct chanlog_list *) malloc(sizeof (struct chanlog_list)); if (!cl) { log("Error : malloc(log(%s)) : %s", chan, strerror(errno)); return; } cl->suivant = NULL; strncpy(cl->name, chan, 32); cl->lognextfree = 0; for (i = 0; i < LOGLEN; i++) cl->log[i] = NULL; log(" join %s", chan); } /* }}} */ /* {{{ clientparts */ /* appelé quand le client part d'un chan * vire le log si il existe */ void clientparts(char *chan) { struct chanlog_list *cl = prvlog.suivant, *clp = &prvlog; register int i; while (cl) { if (!strncasecmp(cl->name, chan, 32)) { /* on retire le maillon de la liste */ clp->suivant = cl->suivant; /* et on les free() */ for (i = 0; i < LOGLEN; i++) free(cl->log[i]); free(cl); cl = clp; } /* et on continue avec le reste de la liste */ clp = cl; cl = cl->suivant; } log(" part %s", chan); } /* }}} */ /* {{{ processmodecmd */ void processmodecmd(char *modestr) { // log( "Debug : mode %s\n", modestr ); } /* }}} */ /* {{{ processprivmsgcmd */ void processprivmsgcmd(char *talker, char *dest, char *privmsg) { struct chanlog_list *cl = &prvlog; char *logphr; int len; int pv = 0; if (!strncasecmp(dest, curnick, strlen(curnick)) && !strncasecmp(privmsg, "\001VERSION\001", 9)) { log("demande de version !"); sendphrase(&serverstruct, "NOTICE %s :\001VERSION %s v%s\001\r\n", talker, bname, version); } if ((dest[0] == '&') || (dest[0] == '#')) { /* si le message est destiné a un chan, on regarde si le log existe */ while ((cl = cl->suivant) && strncasecmp(cl->name, dest, 32)) ; if (!cl) /* si le log n'existe pas on le créé */ clientjoins(dest); /* et on vérifie que l'allocation du nouveau log est effective */ cl = &prvlog; while ((cl = cl->suivant) && strncasecmp(cl->name, dest, 32)) ; if (!cl) return; } else { /* si le message n'est pas destiné à un chan */ if (!talker[0]) /* la c'est le client qui parle en pv */ pv = 1; else dest = prvlog.name; } if (!talker[0]) talker = curnick; /* on alloue un buffer de PHLEN */ logphr = (char *) malloc(PHLEN); if (!logphr) { log("Error : addlog : malloc() : %s", strerror(errno)); return; } /* fabrique la chaine a archiver */ if (pv) len = snprintf(logphr, PHLEN, ":%s_%s->%s PRIVMSG %s :%s\r\n", timestamp(), talker, dest, prvlog.name, privmsg); else len = snprintf(logphr, PHLEN, ":%s_%s PRIVMSG %s :%s\r\n", timestamp(), talker, dest, privmsg); if (len < PHLEN) /* la place réservée etait trop grande */ logphr = realloc(logphr, len); //log( "Debug : %s.log[%d] = %s", cl->name, cl->lognextfree, logphr ); /* et l'archive a la place de l'ancienne */ free(cl->log[cl->lognextfree]); cl->log[cl->lognextfree] = logphr; /* met a jour le pointeur sur la prochaine case */ cl->lognextfree = LOGNEXT(cl->lognextfree); } /* }}} */ /* {{{ connectserver */ /* connecte le bnc au serveur ircserverhost:ircserverport */ void connectircserver(void) { int newsock = -1; int sock; sendphrase(&clientstruct, ":%s NOTICE %s :connection à %s:%d...\r\n", sname, curnick, ircserverhost, ircserverport); if (serverstruct.socketdesc >= 0) { /* un serveur est déjà au bout du fil : on raccroche */ sendphrase(&serverstruct, "QUIT :%s\r\n", "server hop"); log("Deconnection du serveur pour server hop"); usleep(200000); /* on laisse un peu de temps pour transmettre */ /* et on coupe */ sock = serverstruct.socketdesc; serverstruct.socketdesc = -1; endstruct_reinit(&serverstruct); close(sock); } newsock = connect_socket_byname(ircserverhost, ircserverport); if (newsock < 0) { sendphrase(&clientstruct, ":%s NOTICE %s :impossible de se connecter au serveur %s\r\n", sname, curnick, ircserverhost); return; } log("Connecté au serveur %s:%d", ircserverhost, ircserverport); serverstruct.socketdesc = newsock; /* enregistrement aupres du nouveau serveur */ sendphrase(&serverstruct, "NICK %s\r\n", curnick); sendphrase(&serverstruct, "USER toto \"bouh\" \"\" :toto\r\n"); sendphrase(&serverstruct, "USERHOST %s\r\n", curnick); } /* }}} */ /* {{{ processnewclient */ /* envoie les join, topic, nicks etc */ void processnewclient(void) { struct chanlog_list *cl; /* on sort de l'état away */ sendphrase(&serverstruct, "AWAY\r\n"); /* messages de bienvenue */ sendphrase(&clientstruct, ":%s 001 %s :Welcome to the Internet Relay Network %s!%s@%s\r\n", sname, curnick, curnick, identname, identhost); sendphrase(&clientstruct, ":%s 002 %s :you host is %s, running version %s\r\n", sname, curnick, sname, version); sendphrase(&clientstruct, ":%s 003 %s :This server was created %s\r\n", sname, curnick, uptime); sendphrase(&clientstruct, ":%s 004 %s %s %s %s %s\r\n", sname, curnick, bname, version, "oiwscrknfydaAbghe" /* modes client */ , "biklLmMnoprRstvc" /* modes chan */ ); sendphrase(&clientstruct, ":%s 005 %s :Today is %s\r\n", sname, curnick, timestamp()); sendphrase(&clientstruct, ":%s 376 %s :Okay, connected\r\n", sname, curnick); sendphrase(&serverstruct, "MODE %s\r\n", curnick); usleep(500000); /* informe des chans sur lesquels le bouncer est (croit etre ?) */ cl = &prvlog; while ((cl = cl->suivant)) { sendphrase(&clientstruct, ":%s JOIN %s\r\n", curnick, cl->name); /* questionne le serveur pour que le client recoive les reponses */ sendphrase(&serverstruct, "TOPIC %s\r\n", cl->name); sendphrase(&serverstruct, "MODE %s\r\n", cl->name); usleep(100000); } cl = &prvlog; while ((cl = cl->suivant)) { sendphrase(&serverstruct, "WHO %s\r\n", cl->name); sendphrase(&serverstruct, "NAMES %s\r\n", cl->name); usleep(1000000); } /* ensuite on peut envoyer le log */ playalllog(); } /* }}} */ /* {{{ processserver */ #define ACTION(test) !strcasecmp( action, test ) void processserver(char *phrase) { char *phdup = strtok(strdup(phrase), "\r\n"); char *param = strtok(phdup, " "); char *talker, *action; /* param == talker */ if (param[0] == ':') { /* il y a un talker */ talker = param + 1; action = strtok(NULL, " "); /* on isole le nick du talker */ while ((*(++param)) && *param != '!' && *param != ' ') ; *param = '\0'; /* puis on fait pointer param sur le premier char du premier argument */ param = action + strlen(action) + 1; } else { /* pas de talker (c'est donc le client qui parle) */ talker = ""; action = param; param += strlen(param) + 1; } if (ACTION("ping")) { /* autopong */ sendphrase(&serverstruct, "PONG :%s\r\n", param + 1); goto endserver; } if (ACTION("mode")) processmodecmd(param); /* on ne gere pas les kicks pour ne pas perdre les logs en cas d'autojoin */ /* on peut peut-être gérer les bans alors ? */ /* faudrait aussi gerer les kills pour ne pas les transmettre au client et * se reconnecter tout seul au bout de qq temps */ if (ACTION("privmsg")) { char *dest; dest = strtok(NULL, " "); /* destinataire */ param = dest + strlen(dest) + 1; processprivmsgcmd(talker, dest, param + 1); } if (!strncasecmp(talker, curnick, 32)) { /* c'est une action qui nous concerne */ if (ACTION("nick")) strncpy(curnick, param + 1, 32); if (ACTION("join")) clientjoins(param + 1); if (ACTION("part")) clientparts(strtok(param, " ")); } /* puis on transmet le tout au client */ writephrase(&clientstruct, phrase); endserver: free(phdup); } /* }}} */ /* {{{ processclient */ void processclient(char *phrase) { char *phdup = strtok(strdup(phrase), "\r\n"); char *action = strtok(phdup, " "); char *param = action + strlen(action) + 1; int sock; if (ACTION("bconnect")) { /* requete de connection a un serveur */ param = strtok(NULL, " "); if (param) strncpy(ircserverhost, param, PHLEN); param = strtok(NULL, " "); if (param) ircserverport = atoi(param); /* connection */ connectircserver(); goto endclient; } if (ACTION("bquit")) { sendphrase(&serverstruct, "QUIT :%s\r\n", param); log("Deconnection du serveur par QUIT :%s", param); usleep(200000); /* on coupe */ sock = serverstruct.socketdesc; serverstruct.socketdesc = -1; endstruct_reinit(&serverstruct); close(sock); goto endclient; } if (ACTION("bterminate")) { sendphrase(&clientstruct, ":%s NOTICE %s :%s\r\n", sname, curnick, "Le bouncer se termine"); log("Bterminate recu"); sendphrase(&serverstruct, "QUIT :%s\r\n", "Terminated"); masterrunning = 0; } if (ACTION("playalllog")) { playalllog(); goto endclient; } if (ACTION("clearchanlog")) { if (*param == ':') param++; clearchanlog(param); goto endclient; } if (ACTION("clearlog")) { clearlog(); goto endclient; } if (ACTION("dellog")) { dellog(); goto endclient; } if (ACTION("ping")) { /* autopong */ if (*param == ':') param++; sendphrase(&clientstruct, "PONG :%s\r\n", param); goto endclient; } if (ACTION("quit")) { if (*param == ':') param++; sendphrase(&serverstruct, "AWAY :%s\r\n", (*param) ? param : "detached"); log("Deconnection du client par /quit %s", param); /* et on coupe */ sock = clientstruct.socketdesc; clientstruct.socketdesc = -1; endstruct_reinit(&clientstruct); close(sock); goto endclient; } if (ACTION("nick")) if (newclient) { newclient = 0; /* on informe le client de son vrai nick sur le reseau */ sendphrase(&clientstruct, ":%s NICK :%s\r\n", param, curnick); if (!fork()) { processnewclient(); exit (0); } goto endclient; } if (ACTION("user")) goto endclient; if (ACTION("bjoin")) { clientjoins(param); goto endclient; } if (ACTION("bpart")) { clientparts(param); goto endclient; } if (ACTION("privmsg")) { char *dest; dest = strtok(NULL, " "); /* dest */ param = dest + strlen(dest) + 1; processprivmsgcmd("", dest, param + 1); } /* et on transmet au serveur */ writephrase(&serverstruct, phrase); if (serverstruct.socketdesc < 0) sendphrase(&clientstruct, ":%s NOTICE %s : Non connecté au serveur, mais recu %s\r\n", sname, curnick, phrase); endclient: free(phdup); } #undef ACTION /* }}} */ /* {{{ mainthread */ void mainthread(void) { char *phrase; while (masterrunning) { if ((phrase = readphrase(&serverstruct))) processserver(phrase), free(phrase); if ((phrase = readphrase(&clientstruct))) processclient(phrase), free(phrase); usleep(10000); } } /* }}} */ /* {{{ main */ int main(int argc, char *argv[], char *envp[]) { pthread_t iotid; int i; int lport; void *dummy = NULL; /* initialisation */ signal(SIGPIPE, SIG_IGN); endstruct_init(&clientstruct); endstruct_init(&serverstruct); /* initialisation de la tete du log */ prvlog.suivant = NULL; strncpy(prvlog.name, "#prv", 32); prvlog.lognextfree = 0; for (i = 0; i < LOGLEN; i++) prvlog.log[i] = NULL; /* ouvre le port écouté pour les connection du client irc */ lport = argc > 1 ? atoi(argv[1]) : LPORT; listenedsocket = listen_socket(lport); if (listenedsocket == -1) { log("Abort (socket : %s)", strerror(errno)); exit(1); } masterrunning = 1; iorunning = 1; uptime = strdup(timestamp()); fprintf(stderr, "Bouncer lancé\nport écouté : %d\nfichier de log : %s\n", lport, logfile); /* passage en arrière plan */ fclose(stdin); fclose(stdout); fclose(stderr); if (fork()) exit(0); /* lance le thread io */ if (pthread_create(&iotid, NULL, io_thread, NULL)) { log("Error : pthread_create() : %s", strerror(errno)); exit(1); } log("Bouncer lance, port %d, pid %d", lport, getpid()); /* execute le thread mainthread */ mainthread(); iorunning = 0; /* termine le programme */ pthread_join(iotid, dummy); free(uptime); if (clientstruct.socketdesc >= 0) close(clientstruct.socketdesc); if (serverstruct.socketdesc >= 0) close(clientstruct.socketdesc); log("Terminated.\n\n"); return 0; } /* }}} */