merge my changes to master - ii - irc it, simple FIFO based irc client | |
git clone git://git.suckless.org/ii | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 704ab925e92097778821d36954699f665028254d | |
parent 714bd0cb6ae1eae57ca69b4dbe9fb5243c7a9456 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Fri, 2 Jun 2017 09:54:54 +0200 | |
merge my changes to master | |
Special thanks to: | |
- Wolfgang Corcoran-Mathe for sending patches and testing. | |
- Nico Golde for maintaining ii for so long. | |
Diffstat: | |
M .gitignore | 2 ++ | |
M CHANGES | 31 +++++++++++++++++++++++++++++… | |
M FAQ | 19 ++++++++++++++++++- | |
M LICENSE | 5 +++-- | |
M Makefile | 65 ++++++++++++++---------------… | |
M README | 51 +++++++++++++++++++++++++----… | |
A arg.h | 49 +++++++++++++++++++++++++++++… | |
M config.mk | 14 ++++++++------ | |
M ii.1 | 61 ++++++++++++++++-------------… | |
M ii.c | 1045 ++++++++++++++++++++---------… | |
D query.sh | 29 ----------------------------- | |
A strlcpy.c | 32 +++++++++++++++++++++++++++++… | |
12 files changed, 938 insertions(+), 465 deletions(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -1 +1,3 @@ | |
+ii | |
+*.a | |
*.o | |
diff --git a/CHANGES b/CHANGES | |
@@ -1,3 +1,34 @@ | |
+1.8 (2017-??-??, unreleased): | |
+ - prevent nick collisions by only setting the nick after the server | |
+ accepted it and print a message about change to server log. | |
+ - remove query.sh. | |
+ - add OpenBSD pledge(2) support. | |
+ - fix QUIT message string. | |
+ - raw IRC output to stdout. | |
+ - add quit command (/q [string]). | |
+ - write timestamp in outfile as UNIX timestamp (UTC+0). | |
+ - server host (-s) doesn't default to irc.freenode and is now required. | |
+ - add option (-u) to connect directly to a UNIX domain socket, this | |
+ is useful for tunneling connections. | |
+ - remove "in" file when leaving a channel (enabled commented code). | |
+ - remove "in" files on exit. | |
+ - use IRC_MAX (512), instead of PIPE_BUF (4096) on most systems. | |
+ PIPE_BUF is guaranteed to be atleast 512 bytes for atomic operations. | |
+ - Makefile: always be verbose. | |
+ - use C99 and -D_DEFAULT_SOURCE | |
+ - remove obsolete gethostbyname, use getaddrinfo. | |
+ - IPV6 support. | |
+ - timeout now uses separate exit statuscode 2. | |
+ - cleanup: | |
+ - use arg.h for command-line option parsing. | |
+ - use sbase util functions (estrtol, eprintf). | |
+ - use and import OpenBSD strlcpy(). | |
+ - man page typos. | |
+ - style: | |
+ - linewrap to 79 characters. | |
+ - coding style fixes. | |
+ - non-roman numerals for LICENSE period. | |
+ | |
1.7 (2013-01-05) | |
- -k now specifies an environment variable that contains the | |
server key. This behaviour has been changed in order to not | |
diff --git a/FAQ b/FAQ | |
@@ -6,6 +6,7 @@ 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 | |
@@ -14,12 +15,14 @@ the FIFOs and output files. Feel free to implement or wait … | |
this. Actually I use ii in combination with vim, multitail and screen and it w… | |
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 | |
@@ -28,9 +31,23 @@ To get an instant notice of a new file other mechanisms like… | |
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… | |
+ | |
+ #!/bin/sh | |
+ chan="#yourchannel" | |
+ tail -f "${chan}/out" | while read -r line; do | |
+ cmd=$(printf '%s\n' "$line" | cut -d ' ' -f 4-) | |
+ name=$(printf '%s\n' "$line" | cut -d ' ' -f 3 | tr -d '<>') | |
+ if [ "$cmd" = "!rand" ]; then | |
+ r="$RANDOM" | |
+ if expr "$r" "%" "10"; then | |
+ echo "$name: $r" >> "${chan}/in" | |
+ fi | |
+ 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 | |
@@ -1,7 +1,8 @@ | |
MIT/X Consortium License | |
-(C)opyright MMV-MMVI Anselm R. Garbe <[email protected]> | |
-(C)opyright MMV-MMVIII Nico Golde <nico at ngolde dot de> | |
+(C)opyright 2005-2006 Anselm R. Garbe <[email protected]> | |
+(C)opyright 2005-2011 Nico Golde <nico at ngolde dot de> | |
+(C)opyright 2014-2017 Hiltjo Posthuma <hiltjo at codemadness dot org> | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
diff --git a/Makefile b/Makefile | |
@@ -1,55 +1,48 @@ | |
-# ii - irc it - simple but flexible IRC client | |
-# (C)opyright MMV-MMVI Anselm R. Garbe | |
-# (C)opyright MMV-MMVII Anselm R. Garbe, Nico Golde | |
- | |
+# See LICENSE file for copyright and license details. | |
include config.mk | |
-SRC = ii.c | |
-OBJ = ${SRC:.c=.o} | |
+SRC = ii.c strlcpy.c | |
+OBJ = ${SRC:.c=.o} | |
-all: options ii | |
- @echo built ii | |
+all: 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} $< | |
+.o: | |
+ $(LD) -o $@ $< $(LDFLAGS) | |
-dist: clean | |
- @mkdir -p ii-${VERSION} | |
- @cp -R query.sh Makefile CHANGES README FAQ LICENSE config.mk ii.c ii.… | |
- @tar -cf ii-${VERSION}.tar ii-${VERSION} | |
- @gzip ii-${VERSION}.tar | |
- @rm -rf ii-${VERSION} | |
- @echo created distribution ii-${VERSION}.tar.gz | |
+.c.o: | |
+ $(CC) -c -o $@ $< $(CFLAGS) | |
ii: ${OBJ} | |
- @echo LD $@ | |
- @${CC} -o $@ ${OBJ} ${LDFLAGS} | |
+ ${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" | |
+ mkdir -p ${DESTDIR}${DOCDIR} | |
+ mkdir -p ${DESTDIR}${BINDIR} | |
+ mkdir -p ${DESTDIR}${MAN1DIR} | |
+ install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MAN1DIR} | |
+ install -m 644 CHANGES README FAQ LICENSE ${DESTDIR}${DOCDIR} | |
+ install -m 775 ii ${DESTDIR}${BINDIR} | |
+ sed "s/VERSION/${VERSION}/g" < ii.1 > ${DESTDIR}${MAN1DIR}/ii.1 | |
+ chmod 644 ${DESTDIR}${MAN1DIR}/ii.1 | |
uninstall: all | |
- @rm -f ${DESTDIR}${MAN1DIR}/ii.1 | |
- @rm -rf ${DESTDIR}${DOCDIR} | |
- @rm -f ${DESTDIR}${BINDIR}/ii | |
- @echo "uninstalled ii" | |
+ rm -f ${DESTDIR}${MAN1DIR}/ii.1 \ | |
+ ${DESTDIR}${BINDIR}/ii | |
+ rm -rf ${DESTDIR}${DOCDIR} | |
+ | |
+dist: clean | |
+ mkdir -p ii-${VERSION} | |
+ cp -R Makefile CHANGES README FAQ LICENSE strlcpy.c arg.h \ | |
+ config.mk ii.c ii.1 ii-${VERSION} | |
+ tar -cf ii-${VERSION}.tar ii-${VERSION} | |
+ gzip ii-${VERSION}.tar | |
+ rm -rf ii-${VERSION} | |
clean: | |
- rm -f ii *~ *.o *core *.tar.gz | |
+ rm -f ii *.o | |
diff --git a/README b/README | |
@@ -1,7 +1,7 @@ | |
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 tree with server, channel and nick name directories. In every | |
directory a FIFO file (in) and normal file (out) is placed. | |
The in file is used to communicate with the servers and the out files include | |
@@ -13,6 +13,7 @@ standard command line tools. For example if you want to join… | |
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 | |
@@ -23,6 +24,7 @@ necessary as root): | |
$ make clean install | |
+ | |
Running ii | |
------------ | |
Simply invoke the 'ii' command with required arguments | |
@@ -41,19 +43,50 @@ 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 | |
+ | |
+SSL/TLS support | |
+--------------- | |
+ | |
+Below is an example using OpenBSD relayd which sets up a TCP TLS relay | |
+connection on localhost. A similar setup can be accomplished using | |
+stunnel or netcat with TLS support. This also works for other programs | |
+that don't support TLS natively. | |
+ | |
+/etc/relayd.conf: | |
+ | |
+ table <freenode> { irc.freenode.net } | |
+ table <oftc> { irc.oftc.net } | |
+ | |
+ protocol "irctls" { | |
+ tcp { nodelay, sack } | |
+ } | |
+ | |
+ relay "freenode" { | |
+ listen on 127.0.0.1 port 6668 | |
+ protocol "irctls" | |
+ forward with tls to <freenode> port 6697 | |
+ } | |
+ | |
+ relay "oftc" { | |
+ listen on 127.0.0.1 port 6669 | |
+ protocol "irctls" | |
+ forward with tls to <oftc> port 6697 | |
+ } | |
+ | |
+ | |
+Then connect: | |
+ | |
+ ./irc -n nick -u name -s 127.0.0.1 -p 6668 | |
+ ./irc -n nick -u name -s 127.0.0.1 -p 6669 | |
+ | |
+ | |
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/ | |
+please have a look at the commit messages on http://git.suckless.org/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/arg.h b/arg.h | |
@@ -0,0 +1,49 @@ | |
+/* | |
+ * Copy me if you can. | |
+ * by 20h | |
+ */ | |
+ | |
+#ifndef ARG_H__ | |
+#define ARG_H__ | |
+ | |
+extern char *argv0; | |
+ | |
+/* use main(int argc, char *argv[]) */ | |
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ | |
+ argv[0] && argv[0][0] == '-'\ | |
+ && argv[0][1];\ | |
+ argc--, argv++) {\ | |
+ char argc_;\ | |
+ char **argv_;\ | |
+ int brk_;\ | |
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\ | |
+ argv++;\ | |
+ argc--;\ | |
+ break;\ | |
+ }\ | |
+ for (brk_ = 0, argv[0]++, argv_ = argv;\ | |
+ argv[0][0] && !brk_;\ | |
+ argv[0]++) {\ | |
+ if (argv_ != argv)\ | |
+ break;\ | |
+ argc_ = argv[0][0];\ | |
+ switch (argc_) | |
+ | |
+#define ARGEND }\ | |
+ } | |
+ | |
+#define ARGC() argc_ | |
+ | |
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ | |
+ ((x), abort(), (char *)0) :\ | |
+ (brk_ = 1, (argv[0][1] != '\0')?\ | |
+ (&argv[0][1]) :\ | |
+ (argc--, argv++, argv[0]))) | |
+ | |
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ | |
+ (char *)0 :\ | |
+ (brk_ = 1, (argv[0][1] != '\0')?\ | |
+ (&argv[0][1]) :\ | |
+ (argc--, argv++, argv[0]))) | |
+ | |
+#endif | |
diff --git a/config.mk b/config.mk | |
@@ -1,4 +1,5 @@ | |
# Customize to fit your system | |
+VERSION = 1.7 | |
# paths | |
PREFIX = /usr/local | |
@@ -12,17 +13,18 @@ DESTDIR = | |
INCDIR = ${PREFIX}/include | |
LIBDIR = ${PREFIX}/lib | |
-VERSION = 1.7 | |
# 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} | |
+# debug | |
+#CFLAGS = -g -O0 -pedantic -Wall ${INCLUDES} -DVERSION=\"${VERSION}\" -st… | |
+#LDFLAGS = ${LIBS} | |
+ | |
+# release | |
+CFLAGS = -Os ${INCLUDES} -DVERSION=\"${VERSION}\" -std=c99 -D_DEFAULT_SOUR… | |
+LDFLAGS = -s | |
diff --git a/ii.1 b/ii.1 | |
@@ -1,10 +1,6 @@ | |
-.de FN | |
-\fI\|\\$1\|\fP\\$2 | |
-.. | |
-.TH ii 1 | |
+.TH II 1 ii\-VERSION | |
.SH NAME | |
ii \- irc it or irc improved | |
- | |
.SH DESCRIPTION | |
.B ii | |
is a minimalistic FIFO and filesystem based IRC client. | |
@@ -21,23 +17,27 @@ For example if you will join a channel just do echo "/j #ch… | |
and ii creates a new channel directory with in and out file. | |
.SH SYNOPSIS | |
.B ii | |
-.RB [ \-s | |
-.IR servername ] | |
+.RB < \-s | |
+.IR servername > | |
.RB [ \-p | |
.IR port ] | |
.RB [ \-k | |
-.IR environment variable ] | |
+.IR "environment variable" ] | |
.RB [ \-i | |
.IR prefix ] | |
.RB [ \-n | |
.IR nickname ] | |
.RB [ \-f | |
.IR realname ] | |
- | |
+.RB < \-u | |
+.IR sockname > | |
.SH OPTIONS | |
.TP | |
.BI \-s " servername" | |
-lets you override the default servername (irc.freenode.net) | |
+server to connect to, for example: irc.freenode.net | |
+.TP | |
+.BI \-u " sockname" | |
+connect to a UNIX domain socket instead of directly to a server. | |
.TP | |
.BI \-p " port" | |
lets you override the default port (6667) | |
@@ -54,44 +54,47 @@ 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 | |
+.B ~/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 /a " [<message>]" | |
+.BI /a " [<message>]" | |
mark yourself as away | |
.TP | |
-.FN /j " #channel/nickname [<message>]" | |
+.BI /j " #channel/nickname [<message>]" | |
join a channel or open private conversation with user | |
.TP | |
-.FN /l " #channel/nickname" | |
+.BI /l " [reason]" | |
leave a channel or query | |
.TP | |
-.FN /n " nick" | |
+.BI /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… | |
-So if you need /who just write /WHO as described in the RFC to the server in F… | |
+.BI /q " [reason]" | |
+quit ii | |
.TP | |
-.FN "out file usage" | |
-Write wrappers, pagers or use your tools of choice to display the out file con… | |
+.BI /t " topic" | |
+set the topic of a channel | |
+.SH RAW COMMANDS | |
+.LP | |
+Everything which is not a command will be posted into the channel or to the se… | |
+So if you need /who just write /WHO as described in RFC#1459 to the server in … | |
+.SH SSL PROTOCOL SUPPORT | |
+.LP | |
+For TLS/SSL protocol support you can connect to a local tunnel, for example wi… | |
.SH CONTACT | |
-.TP | |
-Write to ii (at) modprobe (dot) de for suggestions, fixes, 7|-|>< ;) etc. | |
+.LP | |
+Subscribe to the mailinglist and write to dev (at) suckless (dot) org for sugg… | |
.SH AUTHORS | |
-Copyright \(co 2005-2006 by Anselm R. Garbe <garbeam (at) gmail (dot) com> and | |
-Copyright \(co 2005-2008 by Nico Golde <nico (at) ngolde (dot) de> | |
+ii engineers, see LICENSE file | |
.SH SEE ALSO | |
.BR echo (1), | |
-.BR tail (1), | |
+.BR tail (1) | |
+.SH BUGS | |
+Please report them! | |
diff --git a/ii.c b/ii.c | |
@@ -1,514 +1,853 @@ | |
-/* (C)opyright MMV-MMVI Anselm R. Garbe <garbeam at gmail dot com> | |
- * (C)opyright MMV-MMXI Nico Golde <nico at ngolde dot de> | |
- * See LICENSE file for license details. */ | |
+/* See LICENSE file for license details. */ | |
+#include <sys/select.h> | |
+#include <sys/socket.h> | |
+#include <sys/stat.h> | |
+#include <sys/types.h> | |
+#include <sys/un.h> | |
+ | |
+#include <ctype.h> | |
#include <errno.h> | |
+#include <fcntl.h> | |
+#include <limits.h> | |
#include <netdb.h> | |
-#include <sys/types.h> | |
-#include <sys/stat.h> | |
-#include <sys/socket.h> | |
-#include <sys/select.h> | |
#include <netinet/in.h> | |
+#include <pwd.h> | |
+#include <signal.h> | |
+#include <stdarg.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
-#include <limits.h> | |
-#include <fcntl.h> | |
#include <string.h> | |
-#include <pwd.h> | |
-#include <signal.h> | |
-#include <ctype.h> | |
#include <time.h> | |
#include <unistd.h> | |
-#define EXIT_TIMEOUT 2 | |
+char *argv0; | |
+ | |
+#include "arg.h" | |
+ | |
+#undef strlcpy | |
+size_t strlcpy(char *, const char *, size_t); | |
+ | |
+#define IRC_CHANNEL_MAX 200 | |
+#define IRC_MSG_MAX 512 /* quaranteed to be <= than PIPE_BUF */ | |
+#define PING_TIMEOUT 300 | |
-#ifndef PIPE_BUF /* For OS that doesn't includes PIPE_BUF in limits.h, FreeBSD… | |
-#define PIPE_BUF _POSIX_PIPE_BUF | |
-#endif | |
-#define PING_TIMEOUT 300 | |
-#define SERVER_PORT 6667 | |
enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LA… | |
typedef struct Channel Channel; | |
struct Channel { | |
- int fd; | |
- char *name; | |
+ int fdin; | |
+ char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */ | |
+ char inpath[PATH_MAX]; /* input path */ | |
+ char outpath[PATH_MAX]; /* output path */ | |
Channel *next; | |
}; | |
-static int irc; | |
-static time_t last_response; | |
+static Channel * channel_add(const char *); | |
+static Channel * channel_find(const char *); | |
+static Channel * channel_join(const char *); | |
+static void channel_leave(Channel *); | |
+static Channel * channel_new(const char *); | |
+static void channel_normalize_name(char *); | |
+static void channel_normalize_path(char *); | |
+static int channel_open(Channel *); | |
+static void channel_print(Channel *, const char *); | |
+static int channel_reopen(Channel *); | |
+static void channel_rm(Channel *); | |
+static void create_dirtree(const char *); | |
+static void create_filepath(char *, size_t, const char *, const char *, c… | |
+static void ewritestr(int, const char *); | |
+static void handle_channels_input(int, Channel *); | |
+static void handle_server_output(int); | |
+static int isnumeric(const char *); | |
+static void loginkey(int, const char *); | |
+static void loginuser(int, const char *, const char *); | |
+static void proc_channels_input(int, Channel *, char *); | |
+static void proc_channels_privmsg(int, Channel *, char *); | |
+static void proc_server_cmd(int, char *); | |
+static int read_line(int, char *, size_t); | |
+static void run(int, const char *); | |
+static void setup(void); | |
+static void sighandler(int); | |
+static int tcpopen(const char *, const char *); | |
+static size_t tokenize(char **, size_t, char *, int); | |
+static int udsopen(const char *); | |
+static void usage(void); | |
+ | |
+static int isrunning = 1; | |
+static time_t last_response = 0; | |
static Channel *channels = NULL; | |
-static char *host = "irc.freenode.net"; | |
-static char nick[32]; /* might change while running */ | |
-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() { | |
- fputs("ii - irc it - " VERSION "\n" | |
- "(C)opyright MMV-MMVI Anselm R. Garbe\n" | |
- "(C)opyright MMV-MMXI Nico Golde\n" | |
- "usage: ii [-i <irc dir>] [-s <host>] [-p <port>]\n" | |
- " [-n <nick>] [-k <password>] [-f <fullname>]\n", stder… | |
- exit(EXIT_FAILURE); | |
+static Channel *channelmaster = NULL; | |
+static char nick[32]; /* active nickname at runtime */ | |
+static char _nick[32]; /* nickname at startup */ | |
+static char ircpath[PATH_MAX]; /* irc dir (-i) */ | |
+static char msg[IRC_MSG_MAX]; /* message buf used for communication */ | |
+ | |
+static void | |
+usage(void) | |
+{ | |
+ fprintf(stderr, "usage: %s <-s host> [-i <irc dir>] [-p <port>] " | |
+ "[-u <sockname>] [-n <nick>] [-k <password>] " | |
+ "[-f <fullname>]\n", argv0); | |
+ exit(1); | |
} | |
-static char *striplower(char *s) { | |
- char *p = NULL; | |
- for(p = s; p && *p; p++) { | |
- if(*p == '/') *p = ','; | |
- *p = tolower(*p); | |
+static void | |
+ewritestr(int fd, const char *s) | |
+{ | |
+ size_t len, off = 0; | |
+ int w = -1; | |
+ | |
+ len = strlen(s); | |
+ for (off = 0; off < len; off += w) { | |
+ if ((w = write(fd, s + off, len - off)) == -1) | |
+ break; | |
+ off += w; | |
+ } | |
+ if (w == -1) { | |
+ fprintf(stderr, "%s: write: %s\n", argv0, strerror(errno)); | |
+ exit(1); | |
} | |
- return s; | |
} | |
-/* creates directories top-down, if necessary */ | |
-static void create_dirtree(const char *dir) { | |
- char tmp[256]; | |
- char *p = NULL; | |
+/* creates directories bottom-up, if necessary */ | |
+static void | |
+create_dirtree(const char *dir) | |
+{ | |
+ char tmp[PATH_MAX], *p; | |
+ struct stat st; | |
size_t len; | |
- snprintf(tmp, sizeof(tmp),"%s",dir); | |
+ | |
+ strlcpy(tmp, dir, sizeof(tmp)); | |
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 = '/'; | |
- } | |
+ if (len > 0 && tmp[len - 1] == '/') | |
+ tmp[len - 1] = '\0'; | |
+ | |
+ if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode)) | |
+ return; /* dir exists */ | |
+ | |
+ for (p = tmp + 1; *p; p++) { | |
+ if (*p != '/') | |
+ continue; | |
+ *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, channel)) | |
- return 0; | |
+static void | |
+channel_normalize_path(char *s) | |
+{ | |
+ for (; *s; s++) { | |
+ if (isalpha(*s)) | |
+ *s = tolower(*s); | |
+ else if (!isdigit(*s) && !strchr(".#&", *s)) | |
+ *s = '_'; | |
+ } | |
+} | |
+ | |
+static void | |
+channel_normalize_name(char *s) | |
+{ | |
+ char *p; | |
+ | |
+ while (*s == '&' || *s == '#') | |
+ s++; | |
+ for (p = s; *s; s++) { | |
+ if (!strchr(" ,&#\x07", *s)) { | |
+ *p = *s; | |
+ p++; | |
+ } | |
+ } | |
+ *p = '\0'; | |
+} | |
+ | |
+static void | |
+create_filepath(char *filepath, size_t len, const char *path, | |
+ const char *channel, const char *suffix) | |
+{ | |
+ int r; | |
+ | |
+ if (channel[0]) { | |
+ r = snprintf(filepath, len, "%s/%s", path, channel); | |
+ if (r < 0 || (size_t)r >= len) | |
+ goto error; | |
create_dirtree(filepath); | |
- return snprintf(filepath, len, "%s/%s/%s", path, channel, file… | |
+ r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix); | |
+ if (r < 0 || (size_t)r >= len) | |
+ goto error; | |
+ } else { | |
+ r = snprintf(filepath, len, "%s/%s", path, suffix); | |
+ if (r < 0 || (size_t)r >= len) | |
+ goto error; | |
} | |
- return snprintf(filepath, len, "%s/%s", path, file); | |
+ return; | |
+ | |
+error: | |
+ fprintf(stderr, "%s: path to irc directory too long\n", argv0); | |
+ exit(1); | |
} | |
-static void create_filepath(char *filepath, size_t len, char *channel, char *s… | |
- if(!get_filepath(filepath, len, striplower(channel), suffix)) { | |
- fputs("ii: path to irc directory too long\n", stderr); | |
- exit(EXIT_FAILURE); | |
+static int | |
+channel_open(Channel *c) | |
+{ | |
+ int fd; | |
+ struct stat st; | |
+ | |
+ /* make "in" fifo if it doesn't exist already. */ | |
+ if (lstat(c->inpath, &st) != -1) { | |
+ if (!(st.st_mode & S_IFIFO)) | |
+ return -1; | |
+ } else if (mkfifo(c->inpath, S_IRWXU)) { | |
+ return -1; | |
} | |
+ c->fdin = -1; | |
+ fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0); | |
+ if (fd == -1) | |
+ return -1; | |
+ c->fdin = fd; | |
+ | |
+ return 0; | |
} | |
-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 int | |
+channel_reopen(Channel *c) | |
+{ | |
+ if (c->fdin > 2) { | |
+ close(c->fdin); | |
+ c->fdin = -1; | |
+ } | |
+ return channel_open(c); | |
} | |
-static void add_channel(char *cname) { | |
+static Channel * | |
+channel_new(const char *name) | |
+{ | |
Channel *c; | |
- int fd; | |
- char *name = striplower(cname); | |
+ char channelpath[PATH_MAX]; | |
+ | |
+ strlcpy(channelpath, name, sizeof(channelpath)); | |
+ channel_normalize_path(channelpath); | |
+ | |
+ if (!(c = calloc(1, sizeof(Channel)))) { | |
+ fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno)); | |
+ exit(1); | |
+ } | |
+ c->next = NULL; | |
+ strlcpy(c->name, name, sizeof(c->name)); | |
+ channel_normalize_name(c->name); | |
+ | |
+ create_filepath(c->inpath, sizeof(c->inpath), ircpath, | |
+ channelpath, "in"); | |
+ create_filepath(c->outpath, sizeof(c->outpath), ircpath, | |
+ channelpath, "out"); | |
+ return c; | |
+} | |
- for(c = channels; c; c = c->next) | |
- if(!strcmp(name, c->name)) | |
- return; /* already handled */ | |
+static Channel * | |
+channel_find(const char *name) | |
+{ | |
+ Channel *c; | |
+ char chan[IRC_CHANNEL_MAX]; | |
- fd = open_channel(name); | |
- if(fd == -1) { | |
- printf("ii: exiting, cannot create in channel: %s\n", name); | |
- exit(EXIT_FAILURE); | |
+ strlcpy(chan, name, sizeof(chan)); | |
+ channel_normalize_name(chan); | |
+ for (c = channels; c; c = c->next) { | |
+ if (!strcmp(chan, c->name)) | |
+ return c; /* already handled */ | |
} | |
- c = calloc(1, sizeof(Channel)); | |
- if(!c) { | |
- perror("ii: cannot allocate memory"); | |
- exit(EXIT_FAILURE); | |
+ return NULL; | |
+} | |
+ | |
+static Channel * | |
+channel_add(const char *name) | |
+{ | |
+ Channel *c; | |
+ | |
+ c = channel_new(name); | |
+ if (channel_open(c) == -1) { | |
+ fprintf(stderr, "%s: cannot create channel: %s: %s\n", | |
+ argv0, name, strerror(errno)); | |
+ free(c); | |
+ return NULL; | |
} | |
- if(!channels) channels = c; | |
- else { | |
+ if (!channels) { | |
+ channels = c; | |
+ } else { | |
c->next = channels; | |
channels = c; | |
} | |
- c->fd = fd; | |
- c->name = strdup(name); | |
+ return c; | |
} | |
-static void rm_channel(Channel *c) { | |
+static Channel * | |
+channel_join(const char *name) | |
+{ | |
+ Channel *c; | |
+ | |
+ if (!(c = channel_find(name))) | |
+ c = channel_add(name); | |
+ return c; | |
+} | |
+ | |
+static void | |
+channel_rm(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) | |
+ | |
+ if (channels == c) { | |
+ channels = channels->next; | |
+ } else { | |
+ for (p = channels; p && p->next != c; p = p->next) | |
+ ; | |
+ if (p && 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 :%… | |
- nick, nick, host, fullname ? fullname : nick); | |
- else snprintf(message, PIPE_BUF, "NICK %s\r\nUSER %s localhost %s :%s\… | |
- nick, nick, host, fullname ? fullname : nick); | |
- write(irc, message, strlen(message)); /* login */ | |
+static void | |
+channel_leave(Channel *c) | |
+{ | |
+ if (c->fdin > 2) { | |
+ close(c->fdin); | |
+ c->fdin = -1; | |
+ } | |
+ /* remove "in" file on leaving the channel */ | |
+ unlink(c->inpath); | |
+ channel_rm(c); | |
+} | |
+ | |
+static void | |
+loginkey(int ircfd, const char *key) | |
+{ | |
+ snprintf(msg, sizeof(msg), "PASS %s\r\n", key); | |
+ ewritestr(ircfd, msg); | |
+} | |
+ | |
+static void | |
+loginuser(int ircfd, const char *host, const char *fullname) | |
+{ | |
+ snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n", | |
+ nick, nick, host, fullname); | |
+ puts(msg); | |
+ ewritestr(ircfd, msg); | |
} | |
-static int tcpopen(unsigned short port) { | |
+static int | |
+udsopen(const char *uds) | |
+{ | |
+ struct sockaddr_un sun; | |
+ size_t len; | |
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); | |
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { | |
+ fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno)); | |
+ exit(1); | |
+ } | |
+ | |
+ sun.sun_family = AF_UNIX; | |
+ if (strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun… | |
+ fprintf(stderr, "%s: UNIX domain socket path truncation\n", ar… | |
+ exit(1); | |
+ } | |
+ len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family); | |
+ if (connect(fd, (struct sockaddr *)&sun, len) == -1) { | |
+ fprintf(stderr, "%s: connect: %s\n", argv0, strerror(errno)); | |
+ exit(1); | |
+ } | |
+ return fd; | |
+} | |
+ | |
+static int | |
+tcpopen(const char *host, const char *service) | |
+{ | |
+ struct addrinfo hints, *res = NULL, *rp; | |
+ int fd = -1, e; | |
+ | |
+ memset(&hints, 0, sizeof(hints)); | |
+ hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */ | |
+ hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */ | |
+ hints.ai_socktype = SOCK_STREAM; | |
+ | |
+ if ((e = getaddrinfo(host, service, &hints, &res))) { | |
+ fprintf(stderr, "%s: getaddrinfo: %s\n", argv0, gai_strerror(e… | |
+ exit(1); | |
} | |
- 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); | |
+ | |
+ for (rp = res; rp; rp = rp->ai_next) { | |
+ fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol… | |
+ if (fd == -1) | |
+ continue; | |
+ if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) { | |
+ close(fd); | |
+ fd = -1; | |
+ continue; | |
+ } | |
+ break; /* success */ | |
} | |
- if(connect(fd, (const struct sockaddr *) &sin, sizeof(sin)) < 0) { | |
- perror("ii: cannot connect to host"); | |
- exit(EXIT_FAILURE); | |
+ if (fd == -1) { | |
+ fprintf(stderr, "%s: could not connect to %s:%s: %s\n", | |
+ argv0, host, service, strerror(errno)); | |
+ exit(1); | |
} | |
+ | |
+ freeaddrinfo(res); | |
return fd; | |
} | |
-static size_t tokenize(char **result, size_t reslen, char *str, char delim) { | |
+static int | |
+isnumeric(const char *s) | |
+{ | |
+ errno = 0; | |
+ strtol(s, NULL, 10); | |
+ return errno == 0; | |
+} | |
+ | |
+static size_t | |
+tokenize(char **result, size_t reslen, char *str, int delim) | |
+{ | |
char *p = NULL, *n = NULL; | |
- size_t i; | |
+ size_t i = 0; | |
- if(!str) | |
- return 0; | |
- for(n = str; *n == ' '; n++); | |
+ for (n = str; *n == ' '; n++) | |
+ ; | |
p = n; | |
- for(i = 0; *n != 0;) { | |
- if(i == reslen) | |
+ while (*n != '\0') { | |
+ if (i >= reslen) | |
return 0; | |
- if(i > TOK_CHAN - TOK_CMD && strtol(result[0], NULL, 10) > 0) … | |
- if(*n == delim) { | |
- *n = 0; | |
+ if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0]… | |
+ delim = ':'; /* workaround non-RFC compliant messages … | |
+ if (*n == delim) { | |
+ *n = '\0'; | |
result[i++] = p; | |
p = ++n; | |
- } else | |
+ } else { | |
n++; | |
+ } | |
} | |
- if(i<reslen && p < n && strlen(p)) | |
+ /* add last entry */ | |
+ if (i < reslen && p < n && p && *p) | |
result[i++] = p; | |
- return i; /* number of tokens */ | |
+ return i; /* number of tokens */ | |
} | |
-static void print_out(char *channel, char *buf) { | |
- static char outfile[256], server[256], buft[18]; | |
- FILE *out = NULL; | |
- time_t t = time(0); | |
- | |
- if(channel) snprintf(server, sizeof(server), "-!- %s", channel); | |
- if(strstr(buf, server)) channel=""; | |
- create_filepath(outfile, sizeof(outfile), channel, "out"); | |
- if(!(out = fopen(outfile, "a"))) return; | |
- if(channel && channel[0]) add_channel(channel); | |
+static void | |
+channel_print(Channel *c, const char *buf) | |
+{ | |
+ FILE *fp = NULL; | |
+ time_t t = time(NULL); | |
- strftime(buft, sizeof(buft), "%F %R", localtime(&t)); | |
- fprintf(out, "%s %s\n", buft, buf); | |
- fclose(out); | |
+ if (!(fp = fopen(c->outpath, "a"))) | |
+ return; | |
+ fprintf(fp, "%lu %s\n", (unsigned long)t, buf); | |
+ fclose(fp); | |
} | |
-static void proc_channels_privmsg(char *channel, char *buf) { | |
- snprintf(message, PIPE_BUF, "<%s> %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_privmsg(int ircfd, Channel *c, char *buf) | |
+{ | |
+ snprintf(msg, sizeof(msg), "<%s> %s", nick, buf); | |
+ channel_print(c, msg); | |
+ snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf); | |
+ ewritestr(ircfd, msg); | |
} | |
-static void proc_channels_input(Channel *c, char *buf) { | |
- /* static char infile[256]; */ | |
+static void | |
+proc_channels_input(int ircfd, Channel *c, char *buf) | |
+{ | |
char *p = NULL; | |
+ size_t buflen; | |
- if(buf[0] != '/' && buf[0] != 0) { | |
- proc_channels_privmsg(c->name, buf); | |
+ if (buf[0] != '/' && buf[0] != '\0') { | |
+ proc_channels_privmsg(ircfd, c, buf); | |
return; | |
} | |
- message[0] = '\0'; | |
- if(buf[2] == ' ' || buf[2] == '\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\… | |
- else snprintf(message, PIPE_BUF, "JOIN %s\r\n"… | |
- add_channel(&buf[3]); | |
- } | |
- else if(p){ | |
- add_channel(&buf[3]); | |
- proc_channels_privmsg(&buf[3], p + 1); | |
+ msg[0] = '\0'; | |
+ if (buf[2] == ' ' || buf[2] == '\0') { | |
+ buflen = strlen(buf); | |
+ switch (buf[1]) { | |
+ case 'j': /* join */ | |
+ if ((p = strchr(&buf[3], ' '))) /* password parameter … | |
+ *p = '\0'; | |
+ if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '… | |
+ (buf[3] == '!')) | |
+ { | |
+ /* password protected channel */ | |
+ if (p) | |
+ snprintf(msg, sizeof(msg), "JOIN %s %s… | |
+ else | |
+ snprintf(msg, sizeof(msg), "JOIN %s\r\… | |
+ channel_join(&buf[3]); | |
+ } else if (p) { | |
+ if ((c = channel_join(&buf[3]))) | |
+ proc_channels_privmsg(ircfd, c, p + 1); | |
return; | |
} | |
break; | |
- case 't': | |
- if(strlen(buf)>=3) snprintf(message, PIPE_BUF, "TOPIC … | |
+ case 't': /* topic */ | |
+ if (buflen >= 3) | |
+ snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n",… | |
break; | |
- case 'a': | |
- if(strlen(buf)>=3){ | |
- snprintf(message, PIPE_BUF, "-!- %s is away \"… | |
- print_out(c->name, message); | |
+ case 'a': /* away */ | |
+ if (buflen >= 3) { | |
+ snprintf(msg, sizeof(msg), "-!- %s is away \"%… | |
+ channel_print(c, msg); | |
} | |
- if(buf[2] == 0 || strlen(buf)<3) /* or used to make el… | |
- snprintf(message, PIPE_BUF, "AWAY\r\n"); | |
+ if (buflen >= 3) | |
+ snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &bu… | |
else | |
- snprintf(message, PIPE_BUF, "AWAY :%s\r\n", &b… | |
+ snprintf(msg, sizeof(msg), "AWAY\r\n"); | |
break; | |
- case 'n': | |
- if(strlen(buf)>=3){ | |
- snprintf(_nick, sizeof(nick),"%s", &buf[3]); | |
- snprintf(message, PIPE_BUF, "NICK %s\r\n", &bu… | |
+ case 'n': /* change nick */ | |
+ if (buflen >= 3) { | |
+ strlcpy(_nick, &buf[3], sizeof(_nick)); | |
+ snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf… | |
} | |
break; | |
- case 'l': | |
- if(c->name[0] == 0) | |
+ case 'l': /* leave */ | |
+ if (c == channelmaster) | |
return; | |
- if(buf[2] == ' ' && strlen(buf)>=3) | |
- snprintf(message, PIPE_BUF, "PART %s :%s\r\n",… | |
+ if (buflen >= 3) | |
+ snprintf(msg, sizeof(msg), "PART %s :%s\r\n", … | |
else | |
- snprintf(message, PIPE_BUF, | |
- "PART %s :ii - 500 SLOC are to… | |
- write(irc, message, strlen(message)); | |
- close(c->fd); | |
- /*create_filepath(infile, sizeof(infile), c->name, "in… | |
- unlink(infile); */ | |
- rm_channel(c); | |
+ snprintf(msg, sizeof(msg), | |
+ "PART %s :leaving\r\n", c->name); | |
+ ewritestr(ircfd, msg); | |
+ channel_leave(c); | |
return; | |
break; | |
- default: | |
- snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]); | |
+ case 'q': /* quit */ | |
+ if (buflen >= 3) | |
+ snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &bu… | |
+ else | |
+ snprintf(msg, sizeof(msg), | |
+ "QUIT %s\r\n", "bye"); | |
+ ewritestr(ircfd, msg); | |
+ isrunning = 0; | |
+ return; | |
+ break; | |
+ default: /* raw IRC command */ | |
+ snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]); | |
break; | |
} | |
- else | |
- snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]); | |
- | |
- if (message[0] != '\0') | |
- write(irc, message, strlen(message)); | |
+ } else { | |
+ /* raw IRC command */ | |
+ snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]); | |
+ } | |
+ if (msg[0] != '\0') | |
+ ewritestr(ircfd, msg); | |
} | |
-static void proc_server_cmd(char *buf) { | |
+static void | |
+proc_server_cmd(int fd, char *buf) | |
+{ | |
+ Channel *c; | |
+ const char *channel; | |
char *argv[TOK_LAST], *cmd = NULL, *p = NULL; | |
- int i; | |
+ unsigned int i; | |
- if(!buf || *buf=='\0') | |
+ if (!buf || buf[0] == '\0') | |
return; | |
- for(i = 0; i < TOK_LAST; i++) | |
+ /* clear tokens */ | |
+ for (i = 0; i < TOK_LAST; i++) | |
argv[i] = NULL; | |
- /* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf> | |
- <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ] | |
- <command> ::= <letter> { <letter> } | <number> <number> <number> | |
- <SPACE> ::= ' ' { ' ' } | |
- <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ] | |
- <middle> ::= <Any *non-empty* sequence of octets not including SP… | |
- or NUL or CR or LF, the first of which may not be ':'> | |
- <trailing> ::= <Any, possibly *empty*, sequence of octets not inclu… | |
- <crlf> ::= CR LF */ | |
- | |
- if(buf[0] == ':') { /* check prefix */ | |
- if (!(p = strchr(buf, ' '))) return; | |
- *p = 0; | |
- for(++p; *p == ' '; p++); | |
+ | |
+ /* check prefix */ | |
+ if (buf[0] == ':') { | |
+ if (!(p = strchr(buf, ' '))) | |
+ return; | |
+ *p = '\0'; | |
+ for (++p; *p == ' '; p++) | |
+ ; | |
cmd = p; | |
argv[TOK_NICKSRV] = &buf[1]; | |
- if((p = strchr(buf, '!'))) { | |
- *p = 0; | |
+ if ((p = strchr(buf, '!'))) { | |
+ *p = '\0'; | |
argv[TOK_USER] = ++p; | |
} | |
- } else | |
+ } else { | |
cmd = buf; | |
+ } | |
/* remove CRLFs */ | |
- for(p = cmd; p && *p != 0; p++) | |
- if(*p == '\r' || *p == '\n') | |
- *p = 0; | |
+ for (p = cmd; p && *p != '\0'; p++) { | |
+ if (*p == '\r' || *p == '\n') | |
+ *p = '\0'; | |
+ } | |
- if((p = strchr(cmd, ':'))) { | |
- *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)); | |
+ if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) { | |
return; | |
- } else if(!argv[TOK_NICKSRV] || !argv[TOK_USER]) { /* server co… | |
- snprintf(message, PIPE_BUF, "%s%s", argv[TOK_ARG] ? argv[TOK_A… | |
- print_out(0, message); | |
+ } else if (!strcmp("PING", argv[TOK_CMD])) { | |
+ snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]); | |
+ ewritestr(fd, msg); | |
return; | |
- } else if(!strncmp("ERROR", argv[TOK_CMD], 6)) | |
- snprintf(message, PIPE_BUF, "-!- error %s", argv[TOK_TEXT] ? a… | |
- else if(!strncmp("JOIN", argv[TOK_CMD], 5) && (argv[TOK_CHAN] || argv[… | |
- if (argv[TOK_TEXT] != NULL) | |
+ } else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) { | |
+ /* server command */ | |
+ snprintf(msg, sizeof(msg), "%s%s", | |
+ argv[TOK_ARG] ? argv[TOK_ARG] : "", | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); | |
+ channel_print(channelmaster, msg); | |
+ return; /* don't process further */ | |
+ } else if (!strcmp("ERROR", argv[TOK_CMD])) | |
+ snprintf(msg, sizeof(msg), "-!- error %s", | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown"); | |
+ else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK… | |
+ if (argv[TOK_TEXT]) | |
argv[TOK_CHAN] = argv[TOK_TEXT]; | |
- snprintf(message, PIPE_BUF, "-!- %s(%s) has joined %s", argv[T… | |
- } else if(!strncmp("PART", argv[TOK_CMD], 5) && argv[TOK_CHAN]) { | |
- snprintf(message, PIPE_BUF, "-!- %s(%s) has left %s", argv[TOK… | |
- } else if(!strncmp("MODE", argv[TOK_CMD], 5)) | |
- snprintf(message, PIPE_BUF, "-!- %s changed mode/%s -> %s %s",… | |
- else if(!strncmp("QUIT", argv[TOK_CMD], 5)) | |
- snprintf(message, PIPE_BUF, "-!- %s(%s) has quit \"%s\"", argv… | |
- else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] && !strcm… | |
- snprintf(nick, sizeof(nick), "%s", _nick); | |
- snprintf(message, PIPE_BUF, "-!- changed nick to \"%s\"", nick… | |
- print_out(NULL, message); | |
- } else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT]) | |
- snprintf(message, PIPE_BUF, "-!- %s changed nick to %s", argv[… | |
- else if(!strncmp("TOPIC", argv[TOK_CMD], 6)) | |
- snprintf(message, PIPE_BUF, "-!- %s changed topic to \"%s\"", … | |
- else if(!strncmp("KICK", argv[TOK_CMD], 5) && argv[TOK_ARG]) | |
- snprintf(message, PIPE_BUF, "-!- %s kicked %s (\"%s\")", argv[… | |
- else if(!strncmp("NOTICE", argv[TOK_CMD], 7)) | |
- snprintf(message, PIPE_BUF, "-!- \"%s\")", argv[TOK_TEXT] ? ar… | |
- else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8)) | |
- snprintf(message, PIPE_BUF, "<%s> %s", argv[TOK_NICKSRV], argv… | |
+ snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s", | |
+ argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CH… | |
+ } else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) { | |
+ snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s", | |
+ argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CH… | |
+ /* if user itself leaves, don't write to channel (don't reopen… | |
+ if (!strcmp(argv[TOK_NICKSRV], nick)) | |
+ return; | |
+ } else if (!strcmp("MODE", argv[TOK_CMD])) { | |
+ snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s", | |
+ argv[TOK_NICKSRV], | |
+ argv[TOK_CHAN] ? argv[TOK_CHAN] : "", | |
+ argv[TOK_ARG] ? argv[TOK_ARG] : "", | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); | |
+ } else if (!strcmp("QUIT", argv[TOK_CMD])) { | |
+ snprintf(msg, sizeof(msg), "-!- %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) && argv[TOK_TEXT] && | |
+ !strcmp(_nick, argv[TOK_TEXT])) { | |
+ strlcpy(nick, _nick, sizeof(nick)); | |
+ snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick); | |
+ channel_print(channelmaster, msg); | |
+ } else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) { | |
+ snprintf(msg, sizeof(msg), "-!- %s changed nick to %s", | |
+ argv[TOK_NICKSRV], argv[TOK_TEXT]); | |
+ } else if (!strcmp("TOPIC", argv[TOK_CMD])) { | |
+ snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"", | |
+ argv[TOK_NICKSRV], | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); | |
+ } else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) { | |
+ snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")", | |
+ argv[TOK_NICKSRV], argv[TOK_ARG], | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); | |
+ } else if (!strcmp("NOTICE", argv[TOK_CMD])) { | |
+ snprintf(msg, sizeof(msg), "-!- \"%s\")", | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); | |
+ } else if (!strcmp("PRIVMSG", argv[TOK_CMD])) { | |
+ snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV], | |
+ argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); | |
+ } else { | |
+ return; /* can't read this message */ | |
+ } | |
+ if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick)) | |
+ channel = argv[TOK_NICKSRV]; | |
else | |
- return; /* can't read this message */ | |
+ channel = argv[TOK_CHAN]; | |
- if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick))) | |
- print_out(argv[TOK_NICKSRV], message); | |
+ if (!channel || channel[0] == '\0') | |
+ c = channelmaster; | |
else | |
- print_out(argv[TOK_CHAN], message); | |
+ c = channel_join(channel); | |
+ if (c) | |
+ channel_print(c, msg); | |
} | |
-static int read_line(int fd, size_t res_len, char *buf) { | |
+static int | |
+read_line(int fd, char *buf, size_t bufsiz) | |
+{ | |
size_t i = 0; | |
- char c = 0; | |
+ char c = '\0'; | |
+ | |
do { | |
- if(read(fd, &c, sizeof(char)) != sizeof(char)) | |
+ 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' */ | |
+ } while (c != '\n' && i < bufsiz); | |
+ 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); | |
+static void | |
+handle_channels_input(int ircfd, Channel *c) | |
+{ | |
+ char buf[IRC_MSG_MAX]; | |
+ | |
+ if (read_line(c->fdin, buf, sizeof(buf)) == -1) { | |
+ if (channel_reopen(c) == -1) | |
+ channel_rm(c); | |
return; | |
} | |
- proc_channels_input(c, buf); | |
+ proc_channels_input(ircfd, 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); | |
+static void | |
+handle_server_output(int ircfd) | |
+{ | |
+ char buf[IRC_MSG_MAX]; | |
+ | |
+ if (read_line(ircfd, buf, sizeof(buf)) == -1) { | |
+ fprintf(stderr, "%s: remote host closed connection: %s\n", | |
+ argv0, strerror(errno)); | |
+ exit(1); | |
} | |
- proc_server_cmd(buf); | |
+ fprintf(stdout, "%lu %s\n", (unsigned long)time(NULL), buf); | |
+ fflush(stdout); | |
+ proc_server_cmd(ircfd, buf); | |
} | |
-static void run() { | |
- Channel *c, *n; | |
- int r, maxfd; | |
- fd_set rd; | |
+static void | |
+sighandler(int sig) | |
+{ | |
+ if (sig == SIGTERM || sig == SIGINT) | |
+ isrunning = 0; | |
+} | |
+ | |
+static void | |
+setup(void) | |
+{ | |
+ struct sigaction sa; | |
+ | |
+ memset(&sa, 0, sizeof(sa)); | |
+ sa.sa_handler = sighandler; | |
+ sigaction(SIGTERM, &sa, NULL); | |
+ sigaction(SIGINT, &sa, NULL); | |
+} | |
+ | |
+static void | |
+run(int ircfd, const char *host) | |
+{ | |
+ Channel *c, *tmp; | |
+ fd_set rdset; | |
struct timeval tv; | |
- char ping_msg[512]; | |
+ char ping_msg[IRC_MSG_MAX]; | |
+ int r, maxfd; | |
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); | |
+ while (isrunning) { | |
+ maxfd = ircfd; | |
+ FD_ZERO(&rdset); | |
+ FD_SET(ircfd, &rdset); | |
+ for (c = channels; c; c = c->next) { | |
+ if (c->fdin > maxfd) | |
+ maxfd = c->fdin; | |
+ FD_SET(c->fdin, &rdset); | |
} | |
- | |
+ memset(&tv, 0, sizeof(tv)); | |
tv.tv_sec = 120; | |
- tv.tv_usec = 0; | |
- r = select(maxfd + 1, &rd, 0, 0, &tv); | |
- if(r < 0) { | |
- if(errno == EINTR) | |
+ r = select(maxfd + 1, &rdset, 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 ti… | |
- exit(EXIT_TIMEOUT); | |
+ fprintf(stderr, "%s: select: %s\n", argv0, strerror(er… | |
+ exit(1); | |
+ } else if (r == 0) { | |
+ if (time(NULL) - last_response >= PING_TIMEOUT) { | |
+ channel_print(channelmaster, "-!- ii shutting … | |
+ exit(2); /* status code 2 for timeout */ | |
} | |
- write(irc, ping_msg, strlen(ping_msg)); | |
+ ewritestr(ircfd, ping_msg); | |
continue; | |
} | |
- if(FD_ISSET(irc, &rd)) { | |
- handle_server_output(); | |
+ if (FD_ISSET(ircfd, &rdset)) { | |
+ handle_server_output(ircfd); | |
last_response = time(NULL); | |
} | |
- for(c = channels; c; c = n) { | |
- n = c->next; | |
- if(FD_ISSET(c->fd, &rd)) | |
- handle_channels_input(c); | |
+ for (c = channels; c; c = tmp) { | |
+ tmp = c->next; | |
+ if (FD_ISSET(c->fdin, &rdset)) | |
+ handle_channels_input(ircfd, 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) { | |
- fputs("ii: getpwuid() failed\n", stderr); | |
- 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')… | |
- | |
- for(i = 1; (i + 1 < argc) && (argv[i][0] == '-'); i++) { | |
- switch (argv[i][1]) { | |
- case 'i': snprintf(prefix,sizeof(prefix),"%s", argv[++… | |
- case 's': host = argv[++i]; break; | |
- case 'p': port = strtol(argv[++i], NULL, 10); break; | |
- case 'n': snprintf(nick,sizeof(nick),"%s", argv[++i]);… | |
- case 'k': key = getenv(argv[++i]); break; | |
- case 'f': fullname = argv[++i]; break; | |
- default: usage(); break; | |
- } | |
+int | |
+main(int argc, char *argv[]) | |
+{ | |
+ Channel *c, *tmp; | |
+ struct passwd *spw; | |
+ const char *key = NULL, *fullname = NULL, *host = ""; | |
+ const char *uds = NULL, *service = "6667"; | |
+ char prefix[PATH_MAX]; | |
+ int ircfd, r; | |
+ | |
+ /* use nickname and home dir of user by default */ | |
+ if (!(spw = getpwuid(getuid()))) { | |
+ fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno)); | |
+ exit(1); | |
} | |
- irc = tcpopen(port); | |
- | |
- #ifdef __OpenBSD__ /* OpenBSD pledge(2) support */ | |
- if (pledge("stdio rpath wpath cpath dpath", NULL) == -1) { | |
- fprintf(stderr, "ii pledge: %s\n", strerror(errno)); | |
- exit(EXIT_FAILURE); | |
- } | |
- #endif | |
+ strlcpy(nick, spw->pw_name, sizeof(nick)); | |
+ snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir); | |
+ | |
+ ARGBEGIN { | |
+ case 'f': | |
+ fullname = EARGF(usage()); | |
+ break; | |
+ case 'i': | |
+ strlcpy(prefix, EARGF(usage()), sizeof(prefix)); | |
+ break; | |
+ case 'k': | |
+ key = getenv(EARGF(usage())); | |
+ break; | |
+ case 'n': | |
+ strlcpy(nick, EARGF(usage()), sizeof(nick)); | |
+ break; | |
+ case 'p': | |
+ service = EARGF(usage()); | |
+ break; | |
+ case 's': | |
+ host = EARGF(usage()); | |
+ break; | |
+ case 'u': | |
+ uds = EARGF(usage()); | |
+ break; | |
+ default: | |
+ usage(); | |
+ break; | |
+ } ARGEND; | |
+ | |
+ if (!*host) | |
+ usage(); | |
+ | |
+ if (uds) | |
+ ircfd = udsopen(uds); | |
+ else | |
+ ircfd = tcpopen(host, service); | |
- if(!snprintf(path, sizeof(path), "%s/%s", prefix, host)) { | |
- fputs("ii: path to irc directory too long\n", stderr); | |
- exit(EXIT_FAILURE); | |
+#ifdef __OpenBSD__ | |
+ /* OpenBSD pledge(2) support */ | |
+ if (pledge("stdio rpath wpath cpath dpath", NULL) == -1) { | |
+ fprintf(stderr, "%s: pledge: %s\n", argv0, strerror(errno)); | |
+ exit(1); | |
} | |
- create_dirtree(path); | |
+#endif | |
- add_channel(""); /* master channel */ | |
- login(key, fullname); | |
- run(); | |
+ r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host); | |
+ if (r < 0 || (size_t)r >= sizeof(ircpath)) { | |
+ fprintf(stderr, "%s: path to irc directory too long\n", argv0); | |
+ exit(1); | |
+ } | |
+ create_dirtree(ircpath); | |
+ | |
+ channelmaster = channel_add(""); /* master channel */ | |
+ if (key) | |
+ loginkey(ircfd, key); | |
+ loginuser(ircfd, host, fullname && *fullname ? fullname : nick); | |
+ setup(); | |
+ run(ircfd, host); | |
+ if (channelmaster) | |
+ channel_leave(channelmaster); | |
+ | |
+ for (c = channels; c; c = tmp) { | |
+ tmp = c->next; | |
+ channel_leave(c); | |
+ } | |
- return EXIT_SUCCESS; | |
+ return 0; | |
} | |
diff --git a/query.sh b/query.sh | |
@@ -1,29 +0,0 @@ | |
-#!/bin/sh | |
-# ---------------------------------------------------- | |
-# Nico Golde <[email protected]> | |
-# 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 … | |
- if [ $? -ne 1 ]; then | |
- # strip server, nickserv and channel out files | |
- echo $i | egrep -v -i "nickserv|#|$IRCPATH/(irc\.freenode\.net|irc\.of… | |
- if [ $? -ne 1 ]; then | |
- printf "new data in: %s\n=========================================… | |
- tail -5 $i | |
- fi | |
- fi | |
-done | |
- | |
-touch $TMPFILE | |
diff --git a/strlcpy.c b/strlcpy.c | |
@@ -0,0 +1,32 @@ | |
+/* Taken from OpenBSD */ | |
+#include <sys/types.h> | |
+#include <string.h> | |
+ | |
+/* | |
+ * Copy src to string dst of size siz. At most siz-1 characters | |
+ * will be copied. Always NUL terminates (unless siz == 0). | |
+ * Returns strlen(src); if retval >= siz, truncation occurred. | |
+ */ | |
+size_t | |
+strlcpy(char *dst, const char *src, size_t siz) | |
+{ | |
+ char *d = dst; | |
+ const char *s = src; | |
+ size_t n = siz; | |
+ | |
+ /* Copy as many bytes as will fit */ | |
+ if (n != 0) { | |
+ while (--n != 0) { | |
+ if ((*d++ = *s++) == '\0') | |
+ break; | |
+ } | |
+ } | |
+ /* Not enough room in dst, add NUL and traverse rest of src */ | |
+ if (n == 0) { | |
+ if (siz != 0) | |
+ *d = '\0'; /* NUL-terminate dst */ | |
+ while (*s++) | |
+ ; | |
+ } | |
+ return(s - src - 1); /* count does not include NUL */ | |
+} |