From e8a24d7bdadc5b91810d3d80a196e56df3e1085a Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Mon, 9 Jul 2012 22:42:22 -0400 Subject: [PATCH] sic - simple IRC client. http://tools.suckless.org/sic --- bin/sic-1.1/LICENSE | 24 +++++ bin/sic-1.1/Makefile | 54 +++++++++++ bin/sic-1.1/README | 22 +++++ bin/sic-1.1/config.mk | 20 ++++ bin/sic-1.1/sic.1 | 47 ++++++++++ bin/sic-1.1/sic.c | 207 ++++++++++++++++++++++++++++++++++++++++++ bin/sic-1.1/util.c | 74 +++++++++++++++ 7 files changed, 448 insertions(+) create mode 100644 bin/sic-1.1/LICENSE create mode 100644 bin/sic-1.1/Makefile create mode 100644 bin/sic-1.1/README create mode 100644 bin/sic-1.1/config.mk create mode 100644 bin/sic-1.1/sic.1 create mode 100644 bin/sic-1.1/sic.c create mode 100644 bin/sic-1.1/util.c diff --git a/bin/sic-1.1/LICENSE b/bin/sic-1.1/LICENSE new file mode 100644 index 0000000..c6d9111 --- /dev/null +++ b/bin/sic-1.1/LICENSE @@ -0,0 +1,24 @@ +MIT/X Consortium License + +© 2005-2009 Anselm R Garbe +© 2008-2009 Jeroen Schot +© 2007-2009 Kris Maglione +© 2005 Nico Golde + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/bin/sic-1.1/Makefile b/bin/sic-1.1/Makefile new file mode 100644 index 0000000..0ee4426 --- /dev/null +++ b/bin/sic-1.1/Makefile @@ -0,0 +1,54 @@ +# sic - simple irc client + +include config.mk + +SRC = sic.c +OBJ = ${SRC:.c=.o} + +all: options sic + +options: + @echo sic build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.mk + +sic: ${OBJ} + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + @echo cleaning + @rm -f sic ${OBJ} sic-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p sic-${VERSION} + @cp -R LICENSE Makefile README config.mk sic.1 ${SRC} sic-${VERSION} + @tar -cf sic-${VERSION}.tar sic-${VERSION} + @gzip sic-${VERSION}.tar + @rm -rf sic-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f sic ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/sic + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g" < sic.1 > ${DESTDIR}${MANPREFIX}/man1/sic.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/sic.1 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/sic + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/sic.1 + +.PHONY: all options clean dist install uninstall diff --git a/bin/sic-1.1/README b/bin/sic-1.1/README new file mode 100644 index 0000000..ee5ecac --- /dev/null +++ b/bin/sic-1.1/README @@ -0,0 +1,22 @@ +sic - simple irc client +======================= +sic is an extremly fast, small and simple irc client. It reads commands from +standard input and prints all server output to standard output. It multiplexes +also all channel traffic into one output, that you don't have to switch +different channel buffers, that's actually a feature. + + +Installation +------------ +Edit config.mk to match your local setup. sic is installed into +/usr/local by default. + +Afterwards enter the following command to build and install sic +(if necessary as root): + + $ make clean install + + +Running sic +----------- +Simply invoke the 'sic' command with the required arguments. diff --git a/bin/sic-1.1/config.mk b/bin/sic-1.1/config.mk new file mode 100644 index 0000000..f8d1751 --- /dev/null +++ b/bin/sic-1.1/config.mk @@ -0,0 +1,20 @@ +# sic version +VERSION = 1.1 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# includes and libs +INCS = -I. -I/usr/include +LIBS = -L/usr/lib -lc + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_GNU_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# compiler and linker +CC = cc diff --git a/bin/sic-1.1/sic.1 b/bin/sic-1.1/sic.1 new file mode 100644 index 0000000..cfc8dc2 --- /dev/null +++ b/bin/sic-1.1/sic.1 @@ -0,0 +1,47 @@ +.TH SIC 1 sic-VERSION +.SH NAME +sic \- simple irc client +.SH SYNOPSIS +.B sic +.RB [ \-h " "] +.RB [ \-p " "] +.RB [ \-n " "] +.RB [ \-k " "] +.RB [ \-v ] +.SH DESCRIPTION +.B sic +is an extremly fast, small and simple irc client. It reads commands from +standard input and prints all server output to standard output. It multiplexes +also all channel traffic into one output, that you don't have to switch +different channel buffers, that's actually a feature. +.SH OPTIONS +.TP +.B \-h +Overrides the default host (irc6.oftc.net) +.TP +.B \-p +Overrides the default port (6667) +.TP +.B \-n +Override the default nick ($USER) +.TP +.B \-k +Specifies the keyword to authenticate your nick on the host +.TP +.BI \-v +Prints version information to standard output, then exits. +.SH COMMANDS +.TP +.B :j #channel +Join a channel +.TP +.B :l #channel +Leave a channel +.TP +.B :m #channel/user msg +Write a message to #channel/user +.TP +.B :s #channel/user +Set default channel/user +.TP +Everything which is not a command is simply send the server. diff --git a/bin/sic-1.1/sic.c b/bin/sic-1.1/sic.c new file mode 100644 index 0000000..9b0b36a --- /dev/null +++ b/bin/sic-1.1/sic.c @@ -0,0 +1,207 @@ + /* See LICENSE file for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +static char *host = "irc.oftc.net"; +static char *port = "ircd"; +static char *password; +static char nick[32]; +static char bufin[4096]; +static char bufout[4096]; +static char channel[256]; +static time_t trespond; +static FILE *srv; + +#include "util.c" + +static void +pout(char *channel, char *fmt, ...) { + static char timestr[18]; + time_t t; + va_list ap; + + va_start(ap, fmt); + vsnprintf(bufout, sizeof bufout, fmt, ap); + va_end(ap); + t = time(NULL); + strftime(timestr, sizeof timestr, "%D %R", localtime(&t)); + fprintf(stdout, "%-12s: %s %s\n", channel, timestr, bufout); +} + +static void +sout(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vsnprintf(bufout, sizeof bufout, fmt, ap); + va_end(ap); + fprintf(srv, "%s\r\n", bufout); +} + +static void +privmsg(char *channel, char *msg) { + if(channel[0] == '\0') { + pout("", "No channel to send to"); + return; + } + pout(channel, "<%s> %s", nick, msg); + sout("PRIVMSG %s :%s", channel, msg); +} + +static void +parsein(char *s) { + char c, *p; + + if(s[0] == '\0') + return; + skip(s, '\n'); + if(s[0] != ':') { + privmsg(channel, s); + return; + } + c = *++s; + if(c != '\0' && isspace(s[1])) { + p = s + 2; + switch(c) { + case 'j': + sout("JOIN %s", p); + if(channel[0] == '\0') + strlcpy(channel, p, sizeof channel); + return; + case 'l': + s = eat(p, isspace, 1); + p = eat(s, isspace, 0); + if(!*s) + s = channel; + if(*p) + *p++ = '\0'; + if(!*p) + p = "sic - 250 LOC are too much!"; + sout("PART %s :%s", s, p); + return; + case 'm': + s = eat(p, isspace, 1); + p = eat(s, isspace, 0); + if(*p) + *p++ = '\0'; + privmsg(s, p); + return; + case 's': + strlcpy(channel, p, sizeof channel); + return; + } + } + sout("%s", s); +} + +static void +parsesrv(char *cmd) { + char *usr, *par, *txt; + + usr = host; + if(!cmd || !*cmd) + return; + if(cmd[0] == ':') { + usr = cmd + 1; + cmd = skip(usr, ' '); + if(cmd[0] == '\0') + return; + skip(usr, '!'); + } + skip(cmd, '\r'); + par = skip(cmd, ' '); + txt = skip(par, ':'); + trim(par); + if(!strcmp("PONG", cmd)) + return; + if(!strcmp("PRIVMSG", cmd)) + pout(par, "<%s> %s", usr, txt); + else if(!strcmp("PING", cmd)) + sout("PONG %s", txt); + else { + pout(usr, ">< %s (%s): %s", cmd, par, txt); + if(!strcmp("NICK", cmd) && !strcmp(usr, nick)) + strlcpy(nick, txt, sizeof nick); + } +} + +int +main(int argc, char *argv[]) { + int i, c; + struct timeval tv; + const char *user = getenv("USER"); + fd_set rd; + + strlcpy(nick, user ? user : "unknown", sizeof nick); + for(i = 1; i < argc; i++) { + c = argv[i][1]; + if(argv[i][0] != '-' || argv[i][2]) + c = -1; + switch(c) { + case 'h': + if(++i < argc) host = argv[i]; + break; + case 'p': + if(++i < argc) port = argv[i]; + break; + case 'n': + if(++i < argc) strlcpy(nick, argv[i], sizeof nick); + break; + case 'k': + if(++i < argc) password = argv[i]; + break; + case 'v': + eprint("sic-"VERSION", © 2005-2009 Kris Maglione, Anselm R. Garbe, Nico Golde\n"); + default: + eprint("usage: sic [-h host] [-p port] [-n nick] [-k keyword] [-v]\n"); + } + } + /* init */ + i = dial(host, port); + srv = fdopen(i, "r+"); + /* login */ + if(password) + sout("PASS %s", password); + sout("NICK %s", nick); + sout("USER %s localhost %s :%s", nick, host, nick); + fflush(srv); + setbuf(stdout, NULL); + setbuf(srv, NULL); + for(;;) { /* main loop */ + FD_ZERO(&rd); + FD_SET(0, &rd); + FD_SET(fileno(srv), &rd); + tv.tv_sec = 120; + tv.tv_usec = 0; + i = select(fileno(srv) + 1, &rd, 0, 0, &tv); + if(i < 0) { + if(errno == EINTR) + continue; + eprint("sic: error on select():"); + } + else if(i == 0) { + if(time(NULL) - trespond >= 300) + eprint("sic shutting down: parse timeout\n"); + sout("PING %s", host); + continue; + } + if(FD_ISSET(fileno(srv), &rd)) { + if(fgets(bufin, sizeof bufin, srv) == NULL) + eprint("sic: remote host closed connection\n"); + parsesrv(bufin); + trespond = time(NULL); + } + if(FD_ISSET(0, &rd)) { + if(fgets(bufin, sizeof bufin, stdin) == NULL) + eprint("sic: broken pipe\n"); + parsein(bufin); + } + } + return 0; +} diff --git a/bin/sic-1.1/util.c b/bin/sic-1.1/util.c new file mode 100644 index 0000000..26953d0 --- /dev/null +++ b/bin/sic-1.1/util.c @@ -0,0 +1,74 @@ +/* See LICENSE file for license details. */ +#include +#include +#include + +static void +eprint(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vsnprintf(bufout, sizeof bufout, fmt, ap); + va_end(ap); + fprintf(stderr, "%s", bufout); + if(fmt[0] && fmt[strlen(fmt) - 1] == ':') + fprintf(stderr, " %s\n", strerror(errno)); + exit(1); +} + +static int +dial(char *host, char *port) { + static struct addrinfo hints; + int srv; + struct addrinfo *res, *r; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if(getaddrinfo(host, port, &hints, &res) != 0) + eprint("error: cannot resolve hostname '%s':", host); + for(r = res; r; r = r->ai_next) { + if((srv = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) == -1) + continue; + if(connect(srv, r->ai_addr, r->ai_addrlen) == 0) + break; + close(srv); + } + freeaddrinfo(res); + if(!r) + eprint("error: cannot connect to host '%s'\n", host); + return srv; +} + +#define strlcpy _strlcpy +static void +strlcpy(char *to, const char *from, int l) { + memccpy(to, from, '\0', l); + to[l-1] = '\0'; +} + +static char * +eat(char *s, int (*p)(int), int r) { + while(s != '\0' && p(*s) == r) + s++; + return s; +} + +static char* +skip(char *s, char c) { + while(*s != c && *s != '\0') + s++; + if(*s != '\0') + *s++ = '\0'; + return s; +} + +static void +trim(char *s) { + char *e; + + e = s + strlen(s) - 1; + while(isspace(*e) && e > s) + e--; + *(e + 1) = '\0'; +}