Initial import in Git. - irc - Unnamed repository; edit this file 'description'… | |
git clone git://vernunftzentrum.de/irc.git | |
Log | |
Files | |
Refs | |
README | |
--- | |
commit 9853570e09cd34fc5fd6a4a0c03055e5012920ec | |
Author: Quentin Carbonneaux <[email protected]> | |
Date: Sat, 10 Mar 2012 13:23:07 +0100 | |
Initial import in Git. | |
Diffstat: | |
.comfile | 1 + | |
.gitignore | 3 +++ | |
irc.c | 520 +++++++++++++++++++++++++++++++ | |
3 files changed, 524 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/.comfile b/.comfile | |
@@ -0,0 +1 @@ | |
+irc.c | |
diff --git a/.gitignore b/.gitignore | |
@@ -0,0 +1,2 @@ | |
+irc | |
+*.sw[po] | |
+\ No newline at end of file | |
diff --git a/irc.c b/irc.c | |
@@ -0,0 +1,520 @@ | |
+/*% cc -g -Wall -lncurses -o # % | |
+ */ | |
+#include <assert.h> | |
+#include <limits.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <stdarg.h> | |
+#include <string.h> | |
+#include <time.h> | |
+#include <errno.h> | |
+ | |
+#include <curses.h> | |
+#include <unistd.h> | |
+#include <arpa/inet.h> | |
+#include <sys/types.h> | |
+#include <sys/socket.h> | |
+#include <sys/select.h> | |
+#include <netinet/in.h> | |
+#include <netinet/tcp.h> | |
+#include <netdb.h> | |
+#include <locale.h> | |
+ | |
+#define SCROLL 15 | |
+#define DATEFMT "%T" | |
+#define PFMT "%-12s < %s" | |
+ | |
+enum { ChanLen = 64, LineLen = 512, MaxChans = 16, BufSz = 2048, LogSz = 4096 … | |
+ | |
+char nick[64]; | |
+char prefix[64]; | |
+int quit; | |
+int sfd; /* Server file descriptor. */ | |
+struct { | |
+ int x; | |
+ int y; | |
+ WINDOW *sw, *mw, *iw; | |
+} scr; /* Screen relative data. */ | |
+int eof; /* EOF reached on server side. */ | |
+struct Chan { | |
+ char name[ChanLen]; | |
+ char *buf, *eol; | |
+ size_t n, sz; /* n is the scoll offset, sz is size of buf. */ | |
+} chl[MaxChans]; | |
+int nch, ch; /* Current number of channels, and current channel. */ | |
+char outb[BufSz], *outp=outb; /* Output buffer. */ | |
+ | |
+static void scmd(char *, char *, char *, char *); | |
+static void tredraw(void); | |
+static void treset(void); | |
+ | |
+static void | |
+panic(const char *m) | |
+{ | |
+ treset(); | |
+ fprintf(stderr, "Panic: %s\n", m); | |
+ exit(1); | |
+} | |
+ | |
+static void | |
+sndf(const char *fmt, ...) | |
+{ | |
+ va_list vl; | |
+ size_t n, l=BufSz-(outp-outb); | |
+ | |
+ if (l<2) return; | |
+ va_start(vl, fmt); | |
+ n=vsnprintf(outp, l-2, fmt, vl); | |
+ va_end(vl); | |
+ outp += n>l-2 ? l-2 : n; | |
+ *outp++ = '\r'; | |
+ *outp++ = '\n'; | |
+} | |
+ | |
+static int | |
+srd(void) | |
+{ | |
+ static char l[BufSz], *p=l; | |
+ char *s, *usr, *cmd, *par, *data; | |
+ int rd; | |
+ | |
+ if (p-l>=BufSz) p=l; /* Input buffer overflow, there should something … | |
+ rd=read(sfd, p, BufSz-(p-l)); | |
+ if (rd<0) { | |
+ if (errno==EINTR) return 1; | |
+ panic("IO error while reading."); | |
+ } | |
+ if (rd==0) return 0; | |
+ p+=rd; | |
+ for (;;) { /* Cycle on all received lines. */ | |
+ if (!(s=memchr(l, '\n', p-l))) | |
+ return 1; | |
+ if (s>l && s[-1]=='\r') | |
+ s[-1]=0; | |
+ *s++ = 0; | |
+ if (*l==':') { | |
+ if (!(cmd=strchr(l, ' '))) goto lskip; | |
+ *cmd++ = 0; | |
+ usr = l+1; | |
+ } else { | |
+ usr = 0; | |
+ cmd = l; | |
+ } | |
+ if (!(par=strchr(cmd, ' '))) goto lskip; | |
+ *par++ = 0; | |
+ if ((data=strchr(par, ':'))) | |
+ *data++ = 0; | |
+ scmd(usr, cmd, par, data); | |
+ lskip: | |
+ memmove(l, s, p-s); | |
+ p-=s-l; | |
+ } | |
+} | |
+ | |
+static int | |
+dial(const char *host, short port) | |
+{ | |
+ int f; | |
+ struct sockaddr_in sin; | |
+ struct addrinfo *ai, hai; | |
+ | |
+ hai.ai_family = AF_INET; | |
+ hai.ai_socktype = SOCK_STREAM; | |
+ hai.ai_flags = hai.ai_protocol = 0; | |
+ if (getaddrinfo(host, 0, &hai, &ai)) | |
+ panic("Cannot resolve host."); | |
+ memcpy(&sin, ai->ai_addr, sizeof sin); | |
+ sin.sin_port = htons(port); | |
+ freeaddrinfo(ai); | |
+ f = socket(AF_INET, SOCK_STREAM, 0); | |
+ if (f<0) | |
+ panic("Cannot create socket."); | |
+ if (connect(f, (struct sockaddr *)&sin, sizeof sin)<0) | |
+ panic("Cannot connect to host."); | |
+ return f; | |
+} | |
+ | |
+static int | |
+chadd(char *name) | |
+{ | |
+ if (nch>=MaxChans || strlen(name)>=ChanLen) | |
+ return -1; | |
+ strcpy(chl[nch].name, name); | |
+ chl[nch].sz=LogSz; | |
+ chl[nch].buf=malloc(LogSz); | |
+ if (!chl[nch].buf) | |
+ panic("Out of memory."); | |
+ chl[nch].eol=chl[nch].buf; | |
+ chl[nch].n=0; | |
+ ch=nch; | |
+ return nch++; | |
+} | |
+ | |
+static inline int | |
+chfind(char *name) | |
+{ | |
+ int i; | |
+ | |
+ assert(name); | |
+ for (i=nch-1; i>0; i--) | |
+ if (!strcmp(chl[i].name, name)) | |
+ break; | |
+ return i; | |
+} | |
+ | |
+static int | |
+chdel(char *name) | |
+{ | |
+ int n; | |
+ | |
+ if (!(n=chfind(name))) return 0; | |
+ nch--; | |
+ free(chl[n].buf); | |
+ memmove(&chl[n], &chl[n+1], (nch-n)*sizeof(struct Chan)); | |
+ ch=nch-1; | |
+ return 1; | |
+} | |
+ | |
+static void | |
+pushm(int cn, char *msg) | |
+{ | |
+ struct Chan * const c=&chl[cn]; | |
+ size_t blen=c->eol-c->buf, l=strlen(msg); | |
+ | |
+ if (blen+l>=c->sz) { | |
+ do | |
+ c->sz *= 2; | |
+ while (blen+l>=c->sz); | |
+ c->buf=realloc(c->buf, c->sz); | |
+ if (!c->buf) panic("Out of memory."); | |
+ c->eol = c->buf+blen; | |
+ } | |
+ strcpy(c->eol, msg); | |
+ c->eol+=l; | |
+ if (cn==ch && c->n==0) /* Redraw if the current channel was modified. … | |
+ tredraw(); | |
+} | |
+ | |
+static void | |
+pushf(int cn, const char *fmt, ...) | |
+{ | |
+ //struct Chan *const c=&chl[cn]; | |
+ char lb[512]; | |
+ size_t n; | |
+ time_t t; | |
+ struct tm *tm; | |
+ va_list vl; | |
+ | |
+ t=time(0); | |
+ tm=localtime(&t); | |
+ if (!tm) | |
+ panic("localtime failed."); | |
+ n=strftime(lb, sizeof lb, DATEFMT, tm); | |
+ lb[n]=' '; | |
+ if (!n) | |
+ panic("strftime failed."); | |
+ va_start(vl, fmt); | |
+ n+=vsnprintf(lb+n+1, sizeof lb-n-3, fmt, vl); | |
+ va_end(vl); | |
+ strcat(lb, "\n"); | |
+ pushm(cn, lb); | |
+} | |
+ | |
+static void | |
+scmd(char *usr, char *cmd, char *par, char *data) | |
+{ | |
+ int s; | |
+ char *pm=strtok(par, " "); | |
+ | |
+ if (!usr) usr="?"; | |
+ else { | |
+ char *bang=strchr(usr, '!'); | |
+ if (bang) | |
+ *bang=0; | |
+ } | |
+ if (!strcmp(cmd, "PRIVMSG")) { | |
+ if (!pm || !data) return; | |
+ pushf(chfind(pm), PFMT, usr, data); | |
+ } else if (!strcmp(cmd, "PING")) { | |
+ sndf("PONG :%s", data?data:"(null)"); | |
+ } else if (!strcmp(cmd, "PART")) { | |
+ if (!pm) return; | |
+ pushf(chfind(pm), "-!- %s has left %s", usr, pm); | |
+ } else if (!strcmp(cmd, "JOIN")) { | |
+ if (!pm) return; | |
+ pushf(chfind(pm), "-!- %s has joined %s", usr, pm); | |
+ } else if (!strcmp(cmd, "470")) { /* Channel forwarding. */ | |
+ char *ch=strtok(0, " "), *fch=strtok(0, " "); | |
+ if (!ch || !fch || !(s=chfind(ch))) return; | |
+ chl[s].name[0] = 0; | |
+ strncat(chl[s].name, fch, ChanLen-1); | |
+ } else if (!strcmp(cmd, "471") || !strcmp(cmd, "473") | |
+ || !strcmp(cmd, "474") || !strcmp(cmd, "475")) { /* Join error.… | |
+ if ((pm=strtok(0, " "))) { | |
+ chdel(pm); | |
+ pushf(0, "-!- Cannot join channel %s (%s)", pm, cmd); | |
+ tredraw(); | |
+ } | |
+ } else if (!strcmp(cmd, "QUIT")) { /* Commands we don't care about. */ | |
+ return; | |
+ } else | |
+ pushf(0, "%s - %s %s", cmd, par, data?data:"(null)"); | |
+} | |
+ | |
+static void | |
+uparse(char *m) | |
+{ | |
+ char *p=m; | |
+ | |
+ if (p[1]!=' ' && p[1]!=0) { | |
+ pmsg: | |
+ if (ch!=0) { | |
+ m+=strspn(m, " "); | |
+ if (!*m) return; | |
+ pushf(ch, PFMT, nick, m); | |
+ sndf("PRIVMSG %s :%s", chl[ch].name, m); | |
+ } | |
+ return; | |
+ } | |
+ switch (*p) { | |
+ case 'j': /* Join channels. */ | |
+ p+=1+(p[1]==' '); | |
+ p=strtok(p, " "); | |
+ while (p) { | |
+ if (chadd(p)<0) break; | |
+ sndf("JOIN %s", p); | |
+ p=strtok(0, " "); | |
+ } | |
+ tredraw(); | |
+ return; | |
+ case 'l': /* Leave channels. */ | |
+ p+=1+(p[1]==' '); | |
+ if (!*p) { | |
+ if (ch==0) return; /* Cannot leave server window. */ | |
+ strcat(p, chl[ch].name); | |
+ } | |
+ p=strtok(p, " "); | |
+ while (p) { | |
+ if (chdel(p)) | |
+ sndf("PART %s", p); | |
+ p=strtok(0, " "); | |
+ } | |
+ tredraw(); | |
+ return; | |
+ case 'm': /* Private message. */ | |
+ m=p+1+(p[1]==' '); | |
+ if (!(p=strchr(m, ' '))) return; | |
+ *p++ = 0; | |
+ sndf("PRIVMSG %s :%s", m, p); | |
+ return; | |
+ case 'r': /* Send raw. */ | |
+ sndf("%s", m); | |
+ return; | |
+ case 'q': /* Quit. */ | |
+ quit=1; | |
+ return; | |
+ default: /* Send on current channel. */ | |
+ goto pmsg; | |
+ } | |
+} | |
+ | |
+static void | |
+tinit(void) | |
+{ | |
+ setlocale(LC_ALL, ""); | |
+ initscr(); | |
+ raw(); | |
+ noecho(); | |
+ getmaxyx(stdscr, scr.y, scr.x); | |
+ if (scr.y<4) panic("Screen too small."); | |
+ if ((scr.sw=newwin(1, scr.x, 0, 0))==0 | |
+ || (scr.mw=newwin(scr.y-2, scr.x, 1, 0))==0 | |
+ || (scr.iw=newwin(1, scr.x, scr.y-1, 0))==0) | |
+ panic("Cannot create windows."); | |
+ keypad(scr.iw, 1); | |
+ if (has_colors()==TRUE) { | |
+ start_color(); | |
+ init_pair(1, COLOR_WHITE, COLOR_BLUE); | |
+ wbkgd(scr.sw, COLOR_PAIR(1)); | |
+ } | |
+} | |
+ | |
+static void | |
+tredraw(void) | |
+{ | |
+ struct Chan * const c=&chl[ch]; | |
+ char *q, *p; | |
+ int llen=0, nl=0; | |
+ | |
+ if (c->eol==c->buf) { | |
+ wclear(scr.mw); | |
+ wrefresh(scr.mw); | |
+ return; | |
+ } | |
+ p=c->eol-1; | |
+ if (c->n) { | |
+ int i=c->n; | |
+ for (; p>c->buf; p--) | |
+ if (*p=='\n' && !i--) break; | |
+ if (p==c->buf) c->n-=i; | |
+ } | |
+ q=p; | |
+ while (nl<scr.y-2) { | |
+ llen=0; | |
+ while (*q!='\n' && q>c->buf) | |
+ q--, llen++; | |
+ nl += 1+llen/scr.x; | |
+ if (q==c->buf) break; | |
+ q--; | |
+ } | |
+ if (q!=c->buf) q+=2; | |
+ for (llen=0; nl>scr.y-2; ) { /* Maybe we must split the top line. */ | |
+ if (q[llen]=='\n' || llen>=scr.x) { | |
+ q+=llen+(q[llen]=='\n'); | |
+ llen=0; | |
+ nl--; | |
+ } else llen++; | |
+ } | |
+ wclear(scr.mw); | |
+ wmove(scr.mw, 0, 0); | |
+ for (; q<p; q++) | |
+ waddch(scr.mw, *q); | |
+ wrefresh(scr.mw); | |
+} | |
+ | |
+static void | |
+tgetch(void) | |
+{ | |
+ static char lb[BufSz]; | |
+ static size_t cu=0, len=0; | |
+ size_t dirty=len+1, i; | |
+ int c; | |
+ | |
+ c=wgetch(scr.iw); | |
+ switch (c) { | |
+ case 0xe: ch=(ch+1)%nch; tredraw(); return; | |
+ case 0x10: ch=(ch+nch-1)%nch; tredraw(); return; | |
+ case KEY_PPAGE: | |
+ chl[ch].n+=SCROLL; | |
+ tredraw(); | |
+ return; | |
+ case KEY_NPAGE: | |
+ chl[ch].n-=SCROLL; | |
+ if (chl[ch].n<0) chl[ch].n=0; | |
+ tredraw(); | |
+ return; | |
+ case 0x1: cu=0; break; | |
+ case 0x5: cu=len; break; | |
+ case 0x2: | |
+ case KEY_LEFT: if (cu) cu--; break; | |
+ case 0x6: | |
+ case KEY_RIGHT: if (cu<len) cu++; break; | |
+ case 0xb: dirty=len=cu; break; | |
+ case 0x15: | |
+ if (cu==0) return; | |
+ len-=cu; | |
+ memmove(lb, &lb[cu], len); | |
+ dirty=cu=0; | |
+ break; | |
+ case KEY_BACKSPACE: | |
+ if (cu==0) return; | |
+ memmove(&lb[cu-1], &lb[cu], len-cu); | |
+ dirty=--cu; | |
+ len--; | |
+ break; | |
+ case '\n': | |
+ if (len==BufSz) len--; | |
+ lb[len]=0; | |
+ uparse(lb); | |
+ dirty=cu=len=0; | |
+ break; | |
+ case KEY_RESIZE: | |
+ getmaxyx(stdscr, scr.y, scr.x); | |
+ if (scr.y<3 || scr.x<10) panic("Screen too small."); | |
+ tredraw(); | |
+ return; | |
+ default: | |
+ if (c>CHAR_MAX || len>=BufSz) return; /* Skip other curses cod… | |
+ memmove(&lb[cu+1], &lb[cu], len-cu); | |
+ dirty=cu; | |
+ len++; | |
+ lb[cu++]=c; | |
+ break; | |
+ } | |
+ /* TODO, add a cleverer printer to deal with long lines. */ | |
+ if (dirty<=len) { | |
+ wmove(scr.iw, 0, strlen(nick)+2+dirty); | |
+ wclrtoeol(scr.iw); | |
+ for (i=dirty; i<len; i++) | |
+ waddch(scr.iw, lb[i]); | |
+ } | |
+ wmove(scr.iw, 0, strlen(nick)+2+cu); | |
+} | |
+ | |
+static void | |
+treset(void) | |
+{ | |
+ if (scr.mw) delwin(scr.mw); | |
+ if (scr.sw) delwin(scr.sw); | |
+ if (scr.iw) delwin(scr.iw); | |
+ endwin(); | |
+} | |
+ | |
+int | |
+main(void) | |
+{ | |
+ const char *user = getenv("USER"); | |
+ | |
+ if (!user) user="Unknown"; | |
+ tinit(); | |
+ chadd("*server*"); | |
+ waddstr(scr.sw, "Welcome in irc."); | |
+ wrefresh(scr.sw); | |
+ strcpy(nick, "_mpu"); | |
+ waddstr(scr.iw, "_mpu< "); | |
+ sfd = dial("chat.freenode.org", 6667); | |
+ sndf("NICK %s", nick); | |
+ sndf("USER brebi 8 * :%s", user); | |
+ sndf("MODE %s +i", nick); | |
+ while (!quit) { | |
+ fd_set rfs, wfs; | |
+ int ret; | |
+ | |
+ FD_ZERO(&wfs); | |
+ FD_ZERO(&rfs); | |
+ FD_SET(0, &rfs); | |
+ FD_SET(sfd, &rfs); | |
+ if (outp!=outb) | |
+ FD_SET(sfd, &wfs); | |
+ ret=select(sfd+1, &rfs, &wfs, 0, 0); | |
+ if (ret<0) { | |
+ if (errno==EINTR) continue; | |
+ panic("Select failed."); | |
+ } | |
+ if (FD_ISSET(sfd, &rfs)) { | |
+ if (!srd()) | |
+ quit=1; | |
+ } | |
+ if (FD_ISSET(sfd, &wfs)) { | |
+ int wr; | |
+ | |
+ wr=write(sfd, outb, outp-outb); | |
+ if (wr<0) { | |
+ if (errno==EINTR) continue; | |
+ panic("Write error."); | |
+ } | |
+ if (wr==0) continue; | |
+ outp-=wr; | |
+ memmove(outb, outb+wr, outp-outb); | |
+ } | |
+ if (FD_ISSET(0, &rfs)) { | |
+ tgetch(); | |
+ wrefresh(scr.iw); | |
+ } | |
+ } | |
+ close(sfd); | |
+ while (nch--) | |
+ free(chl[nch].buf); | |
+ treset(); | |
+ exit(0); | |
+} |