#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef CTRL #define CTRL(x) (x & 037) #define SCROLL 15 #define INDENT 21 #define DATEFMT "%H:%M" #define PFMT " %-12s < %s" #define PFMTHIGH "> %-12s < %s" #define SRV "irc.oftc.net" #define PORT 6667 enum { ChanLen = 64, LineLen = 512, MaxChans = 16, BufSz = 2048, LogSz = 4096 }; char nick[64]; int quit, winchg; int sfd; /* Server file descriptor. */ struct { int x; int y; WINDOW *sw, *mw, *iw; } scr; /* Screen relative data. */ struct Chan { char name[ChanLen]; char *buf, *eol; int n; /* Scroll offset. */ size_t sz; /* size of buf. */ char high; /* Nick highlight. */ char new; /* New message. */ } chl[MaxChans]; int nch, ch; /* Current number of channels, and current channel. */ char outb[BufSz], *outp=outb; /* Output buffer. */ static FILE *logfp; static void scmd(char *, char *, char *, char *); static void tdrawbar(void); 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 better to do. */ 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 = { 0 }; hai.ai_family = AF_INET; hai.ai_socktype = SOCK_STREAM; 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++; tdrawbar(); 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; tdrawbar(); return 1; } static char * pushl(char *p, char *e) { int x; char *w; if ((w=memchr(p, '\n', e-p))) e=w+1; for (w=p, x=0;; p++, x++) { if (x>=scr.x) { waddch(scr.mw, '\n'); for (x=0; x=e || *p==' ' || p-w+INDENT>=scr.x-1) { for (; w=e) return e; } } } static void pushf(int cn, const char *fmt, ...) { struct Chan *const c=&chl[cn]; size_t n, blen=c->eol-c->buf; va_list vl; time_t t; char *s; struct tm *tm, *gmtm; if (blen+LineLen>=c->sz) { c->sz *= 2; c->buf=realloc(c->buf, c->sz); if (!c->buf) panic("Out of memory."); c->eol=c->buf+blen; } t=time(0); if (!(tm=localtime(&t))) panic("Localtime failed."); n=strftime(c->eol, LineLen, DATEFMT, tm); if (!(gmtm=gmtime(&t))) panic("Gmtime failed."); c->eol[n++] = ' '; va_start(vl, fmt); s = c->eol + n; n+=vsnprintf(s, LineLen-n-1, fmt, vl); va_end(vl); if (logfp) { fprintf(logfp, "%-12.12s\t%04d-%02d-%02dT%02d:%02d:%02dZ\t%s\n", c->name, gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm_mday, gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec, s); fflush(logfp); } strcat(c->eol, "\n"); if (n>=LineLen-1) c->eol+=LineLen-1; else c->eol+=n+1; if (cn==ch && c->n==0) { char *p=c->eol-n-1; if (p!=c->buf) waddch(scr.mw, '\n'); pushl(p, c->eol-1); wrefresh(scr.mw); } } static void scmd(char *usr, char *cmd, char *par, char *data) { int s, c; char *pm=strtok(par, " "); if (!usr) usr="?"; else { char *bang=strchr(usr, '!'); if (bang) *bang=0; } if (!strcmp(cmd, "PRIVMSG")) { if (!pm || !data) return; c=chfind(pm); if (strcasestr(data, nick)) { pushf(c, PFMTHIGH, usr, data); chl[c].high |= ch != c; } else pushf(c, PFMT, usr, data); if (ch != c) { chl[c].new=1; tdrawbar(); } } 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); tdrawbar(); } 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 if (!strcmp(cmd, "NOTICE") || !strcmp(cmd, "375") || !strcmp(cmd, "372") || !strcmp(cmd, "376")) { pushf(0, "%s", data?data:""); } else pushf(0, "%s - %s %s", cmd, par, data?data:"(null)"); } static void uparse(char *m) { char *p=m; if (!p[0] || (p[1]!=' ' && p[1]!=0)) { pmsg: if (ch==0) return; 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. */ if (p[1]) sndf("%s", &p[2]); return; case 'q': /* Quit. */ quit=1; return; default: /* Send on current channel. */ goto pmsg; } } static void sigwinch(int sig) { if (sig) winchg=1; } static void tinit(void) { setlocale(LC_ALL, ""); signal(SIGWINCH, sigwinch); 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); scrollok(scr.mw, 1); if (has_colors()==TRUE) { start_color(); init_pair(1, COLOR_WHITE, COLOR_BLUE); wbkgd(scr.sw, COLOR_PAIR(1)); } } static void tresize(void) { struct winsize ws; winchg=0; if (ioctl(0, TIOCGWINSZ, &ws)<0) panic("Ioctl (TIOCGWINSZ) failed."); resizeterm(scr.y=ws.ws_row, scr.x=ws.ws_col); if (scr.y<3 || scr.x<10) panic("Screen too small."); wresize(scr.mw, scr.y-2, scr.x); wresize(scr.iw, 1, scr.x); wresize(scr.sw, 1, scr.x); mvwin(scr.iw, scr.y-1, 0); tredraw(); tdrawbar(); } static void tredraw(void) { struct Chan *const c=&chl[ch]; char *q, *p; int llen=0, nl=-1; 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 (nlc->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); while (q0 && l'), l++; else if (chl[fst].new) waddch(scr.sw, '+'), l++; for (; *p && l=len) return; memmove(&l[cu], &l[cu+1], len-cu-1); dirty=cu; len--; break; case KEY_BACKSPACE: if (cu==0) return; memmove(&l[cu-1], &l[cu], len-cu); dirty=--cu; len--; break; case '\n': l[len]=0; uparse(l); dirty=cu=len=0; break; default: if (c>CHAR_MAX || len>=BufSz-1) return; /* Skip other curses codes. */ memmove(&l[cu+1], &l[cu], len-cu); dirty=cu; len++; l[cu++]=c; break; } while (cu=scr.x/2 ? scr.x/2 : shft; while (cu>=scr.x+shft) dirty=0, shft += scr.x/2; if (dirty<=shft) i=shft; else if (dirty>scr.x+shft || dirty>len) goto mvcur; else i=dirty; wmove(scr.iw, 0, i-shft); wclrtoeol(scr.iw); for (; i-shft=0) switch (o) { case 'h': case '?': usage: fputs("usage: irc [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-h]\n", stderr); exit(0); case 'l': if (!(logfp=fopen(optarg, "a"))) panic("fopen: logfile"); break; case 'n': if (strlen(optarg)>=sizeof nick) goto usage; strcpy(nick, optarg); break; case 'u': user = optarg; break; case 's': server = optarg; break; case 'p': if (!(port=strtol(optarg, 0, 0))) goto usage; break; } if (!nick[0] && ircnick && strlen(ircnick)