From 711cab69c5f072030ddd7f40158e1beb56ada3a2 Mon Sep 17 00:00:00 2001 From: je Date: Sun, 9 Jan 2011 12:19:35 +0100 Subject: [PATCH] Initial commit Imported the source code from ii-1.4.tar.gz[0], no changes have been made to it. ---- [0]: http://dl.suckless.org/tools/ii-1.4.tar.gz SHA1: 56dfb24295984feacb4ad87e0678dca32c7fec7d --- CHANGES | 18 +++ FAQ | 36 +++++ LICENSE | 22 +++ Makefile | 55 +++++++ README | 59 ++++++++ config.mk | 27 ++++ ii.1 | 95 ++++++++++++ ii.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ query.sh | 29 ++++ 9 files changed, 840 insertions(+) create mode 100644 CHANGES create mode 100644 FAQ create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 config.mk create mode 100644 ii.1 create mode 100644 ii.c create mode 100755 query.sh diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..001cdc1 --- /dev/null +++ b/CHANGES @@ -0,0 +1,18 @@ +1.4 (2008-08-09): + - fix directory traversal on servers that support SAJOIN + NOTE: not marking as security relevant as it is only possible to + create directories outside (which is of course annoying) of the irc + hierarchy but not overwriting arbitrary files with the channel name. + - documentation fixes + - general cleanup + +1.3 (2007-07-14): + - server messages about users (QUIT,JOIN) will no longer + go to the user directories but to the server out file to + give an easy method to monitor it and to prevent spamming + the irc directory. + +1.2 (2007-06-23): + - Exit on channel creation failure, thanks Michael Prokop + - Implemented joining of password protected channels + - Removed -v option from the manpage since it's not implemented diff --git a/FAQ b/FAQ new file mode 100644 index 0000000..4aefa6a --- /dev/null +++ b/FAQ @@ -0,0 +1,36 @@ +FAQ +=== + +Where is IRC command xy (ignore etc.)? +-------------------------------------- +ii is for advanced users, please use standard tools like awk, sed and grep for +this. This can be done easily and will not bloat the code. + +Where is a graphical interface? +------------------------------- +Basically ii follows the UNIX philosophy so it is only file based. But it +should be easy to build different interface because they only have to handle +the FIFOs and output files. Feel free to implement or wait until we have done +this. Actually I use ii in combination with vim, multitail and screen and it works +like a charm. + +Which commands are supported? +----------------------------- +j (join or msg), t (topic), a (away), n (nick), l (leave). The missing are +obsolete or can be easily used by typing the IRC commands itself (i.e. /WHO +instead of /who). + +How can I recognize queries? +---------------------------- +ii itself doesn't support this but the queries.sh script is an example +of how to get the new and changed files in your irc directory. +To get an instant notice of a new file other mechanisms like inotify/dnotify +could be used as well but I was too lazy to try it out since the script +is enough for me. + +What other fancy stuff can I do with ii? +---------------------------------------- +It is very easy to write irc bots in ii: +tail -f \#/out | while read foo; do name=echo $foo | awk '{print $2}' | sed 's,<\\(.*\\)>,\\1,'; if 0 -eq expr $RANDOM % 10 then echo "$name: WHAT??" ; fi; done +This will just spam a channel but think about using nagios2irc or you can +use ii to generate channel stats. Your imagination should be boundless. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dd0b65c --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT/X Consortium License + +(C)opyright MMV-MMVI Anselm R. Garbe +(C)opyright MMV-MMVIII 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/Makefile b/Makefile new file mode 100644 index 0000000..0bd4ca5 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +# ii - irc it - simple but flexible IRC client +# (C)opyright MMV-MMVI Anselm R. Garbe +# (C)opyright MMV-MMVII Anselm R. Garbe, Nico Golde + +include config.mk + +SRC = ii.c +OBJ = ${SRC:.c=.o} + +all: options ii + @echo built ii + +options: + @echo ii build options: + @echo "LIBS = ${LIBS}" + @echo "INCLUDES = ${INCLUDES}" + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +dist: clean + @mkdir -p ii-${VERSION} + @cp -R query.sh Makefile CHANGES README FAQ LICENSE config.mk ii.c ii.1 ii-${VERSION} + @tar -cf ii-${VERSION}.tar ii-${VERSION} + @gzip ii-${VERSION}.tar + @rm -rf ii-${VERSION} + @echo created distribution ii-${VERSION}.tar.gz + +ii: ${OBJ} + @echo LD $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +install: all + @mkdir -p ${DESTDIR}${DOCDIR} + @mkdir -p ${DESTDIR}${BINDIR} + @mkdir -p ${DESTDIR}${MAN1DIR} + + @install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MAN1DIR} + @install -m 644 CHANGES README query.sh FAQ LICENSE ${DESTDIR}${DOCDIR} + @install -m 775 ii ${DESTDIR}${BINDIR} + @install -m 444 ii.1 ${DESTDIR}${MAN1DIR} + @echo "installed ii" + +uninstall: all + @rm -f ${DESTDIR}${MAN1DIR}/ii.1 + @rm -rf ${DESTDIR}${DOCDIR} + @rm -f ${DESTDIR}${BINDIR}/ii + @echo "uninstalled ii" + +clean: + rm -f ii *~ *.o *core *.tar.gz diff --git a/README b/README new file mode 100644 index 0000000..585b326 --- /dev/null +++ b/README @@ -0,0 +1,59 @@ +Abstract +-------- +ii is a minimalistic FIFO and filesystem based IRC client. It creates an irc +directory tree with server, channel and nick name directories. In every +directory a FIFO file (in) and and normal file (out) is placed. + +The in file is used to communicate with the servers and the out files include +the server messages. For every channel and every nick name there will be new in +and out files. + +The basic idea of this is to be able to communicate with an IRC server with +standard command line tools. For example if you want to join a channel just do +echo "/j #channel" > in and ii creates a new channel directory with in and out +file. + +Installation +------------ +Edit config.mk to match your local setup. ii is installed into +/usr/local by default. + +Afterwards enter the following command to build and install ii (if +necessary as root): + + $ make clean install + +Running ii +------------ +Simply invoke the 'ii' command with required arguments + +To make ii a bit more comfortable use it in combination with the multitail +program and for example with vim. Run vim in the server directory and use +key mapping like: +map w1 :.w >> \#ii/in +map w2 :.w >> \#wmii/in +to post to channels. + +If you use the next editor line for a new posting you can use ctrl-p for nick +completion if you wrote the nick in the past. +Thanks to Matthias Kopfermann for this hint. + +You can find an example of how this nested environment could look like on: +http://nion.modprobe.de/blog/archives/440-Using-the-ii-irc-client.html + +Configuration +------------- +No configuration is needed. + +Changelog +--------- +Since I missed the chance to add a proper changelog right from the beginning, +please have a look at the commit messages on http://code.suckless.org/hg/ii/ +they are fairly descriptive on releases prior to 1.2. + +Contact +------- +If you want to contact the developers just write a mail to +ii (at) modprobe (dot) de + +-- Nico Golde, Anselm R. Garbe diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..ac9a047 --- /dev/null +++ b/config.mk @@ -0,0 +1,27 @@ +# Customize to fit your system + +# paths +PREFIX = /usr/local +BINDIR = ${PREFIX}/bin +MANDIR = ${PREFIX}/share/man +MAN1DIR = ${MANDIR}/man1 +DOCDIR = ${PREFIX}/share/doc/ii + +# Set the following to install to a different root +DESTDIR = + +INCDIR = ${PREFIX}/include +LIBDIR = ${PREFIX}/lib +VERSION = 1.4 + +# includes and libs +INCLUDES = -I. -I${INCDIR} -I/usr/include +LIBS = -L${LIBDIR} -L/usr/lib -lc +# uncomment and comment other variables for compiling on Solaris +#LIBS = -L${LIBDIR} -L/usr/lib -lc -lsocket -lnsl +#CFLAGS = -g ${INCLUDES} -DVERSION=\"${VERSION}\" + +# compiler +CC = cc +CFLAGS = -g -O0 -W -Wall ${INCLUDES} -DVERSION=\"${VERSION}\" +LDFLAGS = ${LIBS} diff --git a/ii.1 b/ii.1 new file mode 100644 index 0000000..26d73a1 --- /dev/null +++ b/ii.1 @@ -0,0 +1,95 @@ +.de FN +\fI\|\\$1\|\fP\\$2 +.. +.TH ii 1 +.SH NAME +ii \- irc it or irc improved + +.SH DESCRIPTION +.B ii +is a minimalistic FIFO and filesystem based IRC client. +It creates an irc directory tree with server, channel and +nick name directories. +In every directory a FIFO file (in) and and normal file (out) +is placed. This will be for example ~/irc/irc.freenode.net/. +The in file is used to communicate with the servers and the out +files includes the server messages. For every channel and every nick +name there will be new in and out files. +The basic idea of this is to be able to communicate with an IRC +server with basic command line tools. +For example if you will join a channel just do echo "/j #channel" > in +and ii creates a new channel directory with in and out file. +.SH SYNOPSIS +.B ii +.RB [ \-s +.IR servername ] +.RB [ \-p +.IR port ] +.RB [ \-k +.IR password ] +.RB [ \-i +.IR prefix ] +.RB [ \-n +.IR nickname ] +.RB [ \-f +.IR realname ] + +.SH OPTIONS +.TP +.BI \-s " servername" +lets you override the default servername (irc.freenode.net) +.TP +.BI \-p " port" +lets you override the default port (6667) +.TP +.BI \-k " password" +lets you use a password to authenticate your nick on the server +(be aware of the problem that this is visible in the process list, if you +don't want this use a query to submit your password) +.TP +.BI \-i " prefix" +lets you override the default irc path (~/irc) +.TP +.BI \-n " nickname" +lets you override the default nick ($USER) +.TP +.BI \-f " realname" +lets you specify your real name associated with your nick + +.SH DIRECTORIES +.TP +.FN ~/irc +In this directory the irc tree will be created. In this directory you +will find a directory for your server (default: irc.freenode.net) in +which the FIFO and the output file will be stored. +If you join a channel a new directory with the name of the channel +will be created in the ~/irc/$servername/ directory. + +.SH COMMANDS +.TP +.FN /j " #channel/nickname []" +join a channel or open private conversation with user +.TP +.FN /l " #channel/nickname" +leave a channel or query +.TP +.FN /n " nick" +change the nick name +.TP +.FN /t " topic" +set the topic of a channel +.TP +Everything which is not a command will simply be posted into the channel or to the server. +So if you need /who just write /WHO as described in the RFC to the server in FIFO. +.TP +.FN "out file usage" +Write wrappers, pagers or use your tools of choice to display the out file contents (loco, multitail, etc.). +.SH CONTACT +.TP +Write to ii (at) modprobe (dot) de for suggestions, fixes, 7|-|>< ;) etc. +.SH AUTHORS +Copyright \(co 2005-2006 by Anselm R. Garbe and +Copyright \(co 2005-2008 by Nico Golde +.SH SEE ALSO +.BR echo (1), +.BR tail (1), diff --git a/ii.c b/ii.c new file mode 100644 index 0000000..e7d5528 --- /dev/null +++ b/ii.c @@ -0,0 +1,499 @@ +/* + * (C)opyright MMV-MMVI Anselm R. Garbe + * (C)opyright MMV-MMVII Nico Golde + * See LICENSE file for license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PIPE_BUF /* FreeBSD don't know PIPE_BUF */ +#define PIPE_BUF 4096 +#endif + +enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST }; + +typedef struct Channel Channel; +struct Channel { + int fd; + char *name; + Channel *next; +}; + +#define PING_TIMEOUT 300 +#define SERVER_PORT 6667 +static int irc; +static time_t last_response; +static Channel *channels = NULL; +static char *host = "irc.freenode.net"; +static char nick[32]; /* might change while running */ +static char path[_POSIX_PATH_MAX]; +static char message[PIPE_BUF]; /* message buf used for communication */ + +static void usage() { + fprintf(stderr, "%s", + "ii - irc it - " VERSION "\n" + "(C)opyright MMV-MMVI Anselm R. Garbe\n" + "(C)opyright MMV-MMVII Nico Golde\n" + "usage: ii [-i ] [-s ] [-p ]\n" + " [-n ] [-k ] [-f ]\n"); + exit(EXIT_SUCCESS); +} +static char *striplower(char *s) { + char *p = NULL; + for(p = s; p && *p; p++) { + if(*p == '/') *p = '_'; + *p = tolower(*p); + } + return s; +} + +/* creates directories top-down, if necessary */ +static void create_dirtree(const char *dir) { + char tmp[256]; + char *p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp),"%s",dir); + len = strlen(tmp); + if(tmp[len - 1] == '/') + tmp[len - 1] = 0; + for(p = tmp + 1; *p; p++) + if(*p == '/') { + *p = 0; + mkdir(tmp, S_IRWXU); + *p = '/'; + } + mkdir(tmp, S_IRWXU); +} + +static int get_filepath(char *filepath, size_t len, char *channel, char *file) { + if(channel) { + if(!snprintf(filepath, len, "%s/%s", path, striplower(channel))) + return 0; + create_dirtree(filepath); + return snprintf(filepath, len, "%s/%s/%s", path, striplower(channel), file); + } + return snprintf(filepath, len, "%s/%s", path, file); +} + +static void create_filepath(char *filepath, size_t len, char *channel, char *suffix) { + if(!get_filepath(filepath, len, channel, suffix)) { + fprintf(stderr, "%s", "ii: path to irc directory too long\n"); + exit(EXIT_FAILURE); + } +} + +static int open_channel(char *name) { + static char infile[256]; + create_filepath(infile, sizeof(infile), name, "in"); + if(access(infile, F_OK) == -1) + mkfifo(infile, S_IRWXU); + return open(infile, O_RDONLY | O_NONBLOCK, 0); +} + +static void add_channel(char *name) { + Channel *c; + int fd; + + for(c = channels; c; c = c->next) + if(!strcmp(name, c->name)) + return; /* already handled */ + + fd = open_channel(name); + if(fd == -1) { + printf("ii: exiting, cannot create in channel: %s\n", name); + exit(EXIT_FAILURE); + } + c = calloc(1, sizeof(Channel)); + if(!c) { + perror("ii: cannot allocate memory"); + exit(EXIT_FAILURE); + } + if(!channels) channels = c; + else { + c->next = channels; + channels = c; + } + c->fd = fd; + c->name = strdup(name); +} + +static void rm_channel(Channel *c) { + Channel *p; + if(channels == c) channels = channels->next; + else { + for(p = channels; p && p->next != c; p = p->next); + if(p->next == c) + p->next = c->next; + } + free(c->name); + free(c); +} + +static void login(char *key, char *fullname) { + if(key) snprintf(message, PIPE_BUF, + "PASS %s\r\nNICK %s\r\nUSER %s localhost %s :%s\r\n", key, + nick, nick, host, fullname ? fullname : nick); + else snprintf(message, PIPE_BUF, "NICK %s\r\nUSER %s localhost %s :%s\r\n", + nick, nick, host, fullname ? fullname : nick); + + write(irc, message, strlen(message)); /* login */ +} + +static int tcpopen(unsigned short port) { + int fd; + struct sockaddr_in sin; + struct hostent *hp = gethostbyname(host); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + if(!hp) { + perror("ii: cannot retrieve host information"); + exit(EXIT_FAILURE); + } + sin.sin_family = AF_INET; + memcpy(&sin.sin_addr, hp->h_addr, hp->h_length); + sin.sin_port = htons(port); + if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("ii: cannot create socket"); + exit(EXIT_FAILURE); + } + if(connect(fd, (const struct sockaddr *) &sin, sizeof(sin)) < 0) { + perror("ii: cannot connect to host"); + exit(EXIT_FAILURE); + } + return fd; +} + +static size_t tokenize(char **result, size_t reslen, char *str, char delim) { + char *p = NULL, *n = NULL; + size_t i; + + if(!str) + return 0; + for(n = str; *n == ' '; n++); + p = n; + for(i = 0; *n != 0;) { + if(i == reslen) + return 0; + if(i > TOK_CHAN - TOK_CMD && strtol(result[0], NULL, 10) > 0) delim=':'; /* workaround non-RFC compliant messages */ + if(*n == delim) { + *n = 0; + result[i++] = p; + p = ++n; + } else + n++; + } + if(i %s", nick, buf); + print_out(channel, message); + snprintf(message, PIPE_BUF, "PRIVMSG %s :%s\r\n", channel, buf); + write(irc, message, strlen(message)); +} + +static void proc_channels_input(Channel *c, char *buf) { + static char infile[256]; + char *p = NULL; + + if(buf[0] != '/' && buf[0] != 0) { + proc_channels_privmsg(c->name, buf); + return; + } + message[0] = '\0'; + switch (buf[1]) { + case 'j': + p = strchr(&buf[3], ' '); + if(p) *p = 0; + if((buf[3]=='#')||(buf[3]=='&')||(buf[3]=='+')||(buf[3]=='!')){ + if(p) snprintf(message, PIPE_BUF, "JOIN %s %s\r\n", &buf[3], p + 1); /* password protected channel */ + else snprintf(message, PIPE_BUF, "JOIN %s\r\n", &buf[3]); + add_channel(&buf[3]); + } + else { + if(p){ + add_channel(&buf[3]); + proc_channels_privmsg(&buf[3], p + 1); + return; + } + } + break; + case 't': + if(strlen(buf)>=3) snprintf(message, PIPE_BUF, "TOPIC %s :%s\r\n", c->name, &buf[3]); + break; + case 'a': + if(strlen(buf)>=3){ + snprintf(message, PIPE_BUF, "-!- %s is away \"%s\"", nick, &buf[3]); + print_out(c->name, message); + } + if(buf[2] == 0 || strlen(buf)<3) /* or used to make else part safe */ + snprintf(message, PIPE_BUF, "AWAY\r\n"); + else + snprintf(message, PIPE_BUF, "AWAY :%s\r\n", &buf[3]); + break; + case 'n': + if(strlen(buf)>=3){ + snprintf(nick, sizeof(nick),"%s", &buf[3]); + snprintf(message, PIPE_BUF, "NICK %s\r\n", &buf[3]); + } + break; + case 'l': + if(c->name[0] == 0) + return; + if(buf[2] == ' ' && strlen(buf)>=3) + snprintf(message, PIPE_BUF, "PART %s :%s\r\n", c->name, &buf[3]); + else + snprintf(message, PIPE_BUF, + "PART %s :ii - 500 SLOC are too much\r\n", c->name); + write(irc, message, strlen(message)); + close(c->fd); + create_filepath(infile, sizeof(infile), c->name, "in"); + unlink(infile); + rm_channel(c); + return; + break; + default: + snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]); + break; + } + if (message[0] != '\0') + write(irc, message, strlen(message)); +} + +static void proc_server_cmd(char *buf) { + char *argv[TOK_LAST], *cmd = NULL, *p = NULL; + int i; + + if(!buf || *buf=='\0') + return; + + for(i = 0; i < TOK_LAST; i++) + argv[i] = NULL; + /* ::= [':' ] + ::= | [ '!' ] [ '@' ] + ::= { } | + ::= ' ' { ' ' } + ::= [ ':' | ] + ::= + ::= + ::= CR LF */ + + if(buf[0] == ':') { /* check prefix */ + if (!(p = strchr(buf, ' '))) return; + *p = 0; + for(++p; *p == ' '; p++); + cmd = p; + argv[TOK_NICKSRV] = &buf[1]; + if((p = strchr(buf, '!'))) { + *p = 0; + argv[TOK_USER] = ++p; + } + } else + cmd = buf; + + /* remove CRLFs */ + for(p = cmd; p && *p != 0; p++) + if(*p == '\r' || *p == '\n') + *p = 0; + + if((p = strchr(cmd, ':'))) { + *p = 0; + argv[TOK_TEXT] = ++p; + } + + tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' '); + + if(!argv[TOK_CMD] || !strncmp("PONG", argv[TOK_CMD], 5)) { + return; + } else if(!strncmp("PING", argv[TOK_CMD], 5)) { + snprintf(message, PIPE_BUF, "PONG %s\r\n", argv[TOK_TEXT]); + write(irc, message, strlen(message)); + return; + } else if(!argv[TOK_NICKSRV] || !argv[TOK_USER]) { /* server command */ + snprintf(message, PIPE_BUF, "%s%s", argv[TOK_ARG] ? argv[TOK_ARG] : "", argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + print_out(0, message); + return; + } else if(!strncmp("ERROR", argv[TOK_CMD], 6)) + snprintf(message, PIPE_BUF, "-!- error %s", argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown"); + else if(!strncmp("JOIN", argv[TOK_CMD], 5)) { + if(argv[TOK_TEXT] != NULL){ + p = strchr(argv[TOK_TEXT], ' '); + if(p) + *p = 0; + } + argv[TOK_CHAN] = argv[TOK_TEXT]; + snprintf(message, PIPE_BUF, "-!- %s(%s) has joined %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_TEXT]); + } else if(!strncmp("PART", argv[TOK_CMD], 5)) { + snprintf(message, PIPE_BUF, "-!- %s(%s) has left %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); + } else if(!strncmp("MODE", argv[TOK_CMD], 5)) + snprintf(message, PIPE_BUF, "-!- %s changed mode/%s -> %s %s", argv[TOK_NICKSRV], argv[TOK_CMD + 1] ? argv[TOK_CMD + 1] : "" , argv[TOK_CMD + 2]? argv[TOK_CMD + 2] : "", argv[TOK_CMD + 3] ? argv[TOK_CMD + 3] : ""); + else if(!strncmp("QUIT", argv[TOK_CMD], 5)) + snprintf(message, PIPE_BUF, "-!- %s(%s) has quit \"%s\"", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + else if(!strncmp("NICK", argv[TOK_CMD], 5)) + snprintf(message, PIPE_BUF, "-!- %s changed nick to %s", argv[TOK_NICKSRV], argv[TOK_TEXT]); + else if(!strncmp("TOPIC", argv[TOK_CMD], 6)) + snprintf(message, PIPE_BUF, "-!- %s changed topic to \"%s\"", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + else if(!strncmp("KICK", argv[TOK_CMD], 5)) + snprintf(message, PIPE_BUF, "-!- %s kicked %s (\"%s\")", argv[TOK_NICKSRV], argv[TOK_ARG], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + else if(!strncmp("NOTICE", argv[TOK_CMD], 7)) + snprintf(message, PIPE_BUF, "-!- \"%s\")", argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8)) + snprintf(message, PIPE_BUF, "<%s> %s", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick))) + print_out(argv[TOK_NICKSRV], message); + else + print_out(argv[TOK_CHAN], message); +} + +static int read_line(int fd, size_t res_len, char *buf) { + size_t i = 0; + char c = 0; + do { + if(read(fd, &c, sizeof(char)) != sizeof(char)) + return -1; + buf[i++] = c; + } + while(c != '\n' && i < res_len); + buf[i - 1] = 0; /* eliminates '\n' */ + return 0; +} + +static void handle_channels_input(Channel *c) { + static char buf[PIPE_BUF]; + if(read_line(c->fd, PIPE_BUF, buf) == -1) { + close(c->fd); + int fd = open_channel(c->name); + if(fd != -1) + c->fd = fd; + else + rm_channel(c); + return; + } + proc_channels_input(c, buf); +} + +static void handle_server_output() { + static char buf[PIPE_BUF]; + if(read_line(irc, PIPE_BUF, buf) == -1) { + perror("ii: remote host closed connection"); + exit(EXIT_FAILURE); + } + proc_server_cmd(buf); +} + +static void run() { + Channel *c; + int r, maxfd; + fd_set rd; + struct timeval tv; + char ping_msg[512]; + + snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host); + for(;;) { + FD_ZERO(&rd); + maxfd = irc; + FD_SET(irc, &rd); + for(c = channels; c; c = c->next) { + if(maxfd < c->fd) + maxfd = c->fd; + FD_SET(c->fd, &rd); + } + + tv.tv_sec = 120; + tv.tv_usec = 0; + r = select(maxfd + 1, &rd, 0, 0, &tv); + if(r < 0) { + if(errno == EINTR) + continue; + perror("ii: error on select()"); + exit(EXIT_FAILURE); + } else if(r == 0) { + if(time(NULL) - last_response >= PING_TIMEOUT) { + print_out(NULL, "-!- ii shutting down: ping timeout"); + exit(EXIT_FAILURE); + } + write(irc, ping_msg, strlen(ping_msg)); + continue; + } + if(FD_ISSET(irc, &rd)) { + handle_server_output(); + last_response = time(NULL); + } + for(c = channels; c; c = c->next) + if(FD_ISSET(c->fd, &rd)) + handle_channels_input(c); + } +} + +int main(int argc, char *argv[]) { + int i; + unsigned short port = SERVER_PORT; + struct passwd *spw = getpwuid(getuid()); + char *key = NULL, *fullname = NULL; + char prefix[_POSIX_PATH_MAX]; + + if(!spw) { + fprintf(stderr,"ii: getpwuid() failed\n"); + exit(EXIT_FAILURE); + } + snprintf(nick, sizeof(nick), "%s", spw->pw_name); + snprintf(prefix, sizeof(prefix),"%s/irc", spw->pw_dir); + if (argc <= 1 || (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'h')) usage(); + + for(i = 1; (i + 1 < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'i': snprintf(prefix,sizeof(prefix),"%s", argv[++i]); break; + case 's': host = argv[++i]; break; + case 'p': port = strtol(argv[++i], NULL, 10); break; + case 'n': snprintf(nick,sizeof(nick),"%s", argv[++i]); break; + case 'k': key = argv[++i]; break; + case 'f': fullname = argv[++i]; break; + default: usage(); break; + } + } + irc = tcpopen(port); + if(!snprintf(path, sizeof(path), "%s/%s", prefix, host)) { + fprintf(stderr, "%s", "ii: path to irc directory too long\n"); + exit(EXIT_FAILURE); + } + create_dirtree(path); + + add_channel(""); /* master channel */ + login(key, fullname); + run(); + + return 0; +} diff --git a/query.sh b/query.sh new file mode 100755 index 0000000..e33bb03 --- /dev/null +++ b/query.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# ---------------------------------------------------- +# Nico Golde +# License: do whatever you want with this code +# Purpose: locate new queries for the ii irc client +# ---------------------------------------------------- + +IRCPATH=$HOME/irc +TMPFILE=$IRCPATH/queries.tmp + +if [ ! -f $TMPFILE ]; then + touch $TMPFILE +fi + +echo "searching new query data" +for i in `find $IRCPATH -newer $TMPFILE -name 'out'` +do + grep -v '\-!\-' $i > /dev/null 2>&1 # if file doesnt just contain server stuff + if [ $? -ne 1 ]; then + # strip server, nickserv and channel out files + echo $i | egrep -v -i "nickserv|#|$IRCPATH/(irc\.freenode\.net|irc\.oftc\.net)/out" > /dev/null 2>&1 + if [ $? -ne 1 ]; then + echo -e "new data in: $i\n========================================================" + tail -5 $i + fi + fi +done + +touch $TMPFILE -- 2.11.4.GIT