irctk

libircclient binding for scripts
git clone https://a3nm.net/git/irctk/
Log | Files | Refs | README

commit b4f6616478c9439fa4cd57c799b427e856cfe372
parent d6fd65a60ad29c124f0b00627ffb452236647374
Author: Antoine Amarilli <a3nm@a3nm.net>
Date:   Thu, 27 Jun 2013 01:48:32 +0200

Merge branch 'returns'

Conflicts:
	README
	TODO

Diffstat:
Makefile | 2+-
README | 28+++++++++++++++++++---------
TODO | 3+++
irctk.c | 214++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
4 files changed, 175 insertions(+), 72 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ CC = gcc CFLAGS = -Wall -Wno-unused-but-set-variable -O2 -DENABLE_THREADS -D_REENTRANT -LIBS = -lpthread -lnsl -lircclient #-lssl +LIBS = -lpthread -lnsl -lircclient -lssl -lcrypto all: irctk diff --git a/README b/README @@ -24,10 +24,17 @@ quickly, either in the shell or in your favourite programming language. == 2. Installation == -irctk is written using libircclient, you will need this library to compile and -run the program. Install libircclient-dev (on Ubuntu and Debian systems), or get -it from <https://sourceforge.net/projects/libircclient/> and install it -manually. Once this is done, run "make" to compile irctk. +irctk is written using libircclient, you will need this library (version >= 1.7 +with SSL support) to compile and run the program. The package is libircclient1 +and libircclient-dev on Debian but it is outdated as of this writing, hence you +should download the lib from <https://sourceforge.net/projects/libircclient/> +and compile it and install it: + + ./configure --enable-openssl --enable-shared + make + sudo make install + +Once this is done, run "make" to compile irctk. == 3. How to use == @@ -45,9 +52,11 @@ shell from interpreting '#' (irctk won't see them). More elaborate options are supported. Here is how to connect to a password-protected channel on a password-protected server on a non-standard -port, specifying a custom nickname, username and real name. +port, specifying a custom nickname, username and real name, and disabling SSL +certificate checking (for connecting to servers with SSL support and a +self-signed certificate). - $ irctk -U jh -R "James Hacker" nick:srvpass@example.com:3724 '#test:chanpass' + $ irctk -s -U jh -R "J. Hacker" nick:srvpass@example.com:3724 '#test:chanpass' === 3.2. Using irctk's stdin === @@ -419,9 +428,10 @@ which make it easy to write bots in shell script one-liners. * sic <http://tools.suckless.org/sic> -sic is pretty similar to irctk, except irctk hides more things from the -underlying IRC protocol and has more features (granted, this is not necessarily -a good thing). +sic is pretty similar to irctk, except irctk abstracts more things from the +underlying IRC protocol and has more features (e.g., SSL support and the various +options). Conversely, sic is <= 250 LOC without dependency on an external +library. * IrcTK <https://github.com/maxcountryman/irctk> diff --git a/TODO b/TODO @@ -1,9 +1,12 @@ - fix unittests +- default ssl ports +- what happens when we talk but are actually not connected? later: - exit when killed, advertising the signal - use notices and errors - look into sirc-2.211/ssfe.c +- ipv6 support tracking: - use user list given when joining diff --git a/irctk.c b/irctk.c @@ -13,8 +13,8 @@ #include <sys/time.h> #include <unistd.h> -#include <libircclient/libircclient.h> -#include <libircclient/libirc_rfcnumeric.h> +#include <libircclient.h> +#include <libirc_rfcnumeric.h> #define E_SESSION 1 #define E_CONNECT 2 @@ -46,7 +46,7 @@ const char *argp_program_bug_address = "<a3nm@a3nm.net>"; static char doc[] = "irctk -- an IRC toolkit"; static char args_doc[] = "[NICK[:PASS]@]SERVER[:PORT] [CHANNEL[:PASS]]..."; -enum arg_types {STANDARD, TIMING, MISC, CHANNEL_SELECTION, BOT_OPTIONS, +enum arg_types {STANDARD, SSL, TIMING, MISC, CHANNEL_SELECTION, BOT_OPTIONS, TRACKING, PARSING, DISPLAY_SELECTION, NAME}; static struct argp_option options[] = { @@ -58,6 +58,19 @@ static struct argp_option options[] = { "Don't display errors on stderr, fail silently", STANDARD}, + {"ssl", 's', 0, 0, + "Use SSL", + SSL}, + {"no-ssl", 'S', 0, 0, + "Do not use SSL (default)", + SSL}, + {"check-certificate", 'k', 0, 0, + "Require a valid SSL certificate (default)", + SSL}, + {"no-check-certificate", 'K', 0, 0, + "Accept self-signed, invalid (and man-in-the-middle) certificates", + SSL}, + {"interval", 'i', "secs", 0, "Wait delay between posted messages (default: 0 for tty stdin, 1 for" " file/pipe)", @@ -67,7 +80,7 @@ static struct argp_option options[] = { " (default: 2)", TIMING}, {"retry-after", 'y', "secs", 0, - "Wait delay before trying to reconnect to the server (default: 5)", + "Wait delay before trying to reconnect to the server (default: 20)", TIMING}, {"retry-factor", 'Y', "secs", 0, "Retry delay multiplication factor after each failed attempt (default: 2)", @@ -161,14 +174,20 @@ static struct argp_option options[] = { /* The structure to store program settings */ struct arguments { + pthread_mutex_t mutex; + char nick[MAX_NICK_LEN]; char hostname[MAX_NICK_LEN]; + char *raw_server; // before parsing SSL char *server; int port; char *password; char **channels; int n_channels; // TODO2 refactor this with fifos + int ssl; + int ssl_check; + int verbosity; int interval; @@ -211,19 +230,25 @@ struct arguments args; /* The initial argument values */ void initialize_args() { + assert(!pthread_mutex_init(&(args.mutex), NULL)); + strncpy(args.nick, "irctk", MAX_NICK_LEN-1); strncpy(args.hostname, "!~UNKNOWN@UNKNOWN", MAX_NICK_LEN-1); + args.raw_server = NULL; args.server = NULL; args.password = NULL; args.port = 6667; args.channels = (char**) NULL; args.n_channels = 0; + args.ssl = 0; + args.ssl_check = 1; + args.verbosity = 0; args.interval = isatty(fileno(stdin))?0:1000000; args.interval_after = 2000000; - args.retry_after = 5; + args.retry_after = 20; args.retry_factor = 2; args.default_destination = DEFAULT_LAST_IN; @@ -290,6 +315,18 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) case 'q': arguments->verbosity = -1; break; + case 'S': + arguments->ssl = 0; + break; + case 's': + arguments->ssl = 1; + break; + case 'K': + arguments->ssl_check = 0; + break; + case 'k': + arguments->ssl_check = 1; + break; case 'n': arguments->event_to = NOTHING; break; @@ -387,7 +424,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) if (state->arg_num == 0) { i = -1; arguments->raw_dest = arg; - arguments->server = arg; + arguments->raw_server = arg; int saw_colon = 0; int saw_at = 0; @@ -400,7 +437,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) if (arguments->raw_dest[i] == '@') { arguments->raw_dest[i] = 0; saw_at = i; - arguments->server = arguments->raw_dest + saw_at + 1; + arguments->raw_server = arguments->raw_dest + saw_at + 1; set_nick_and_names(arguments); if (saw_colon) { arguments->password = arguments->raw_dest + saw_colon + 1; @@ -423,7 +460,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) break; case ARGP_KEY_END: - if (state->arg_num < 1 || strlen(arguments->server) == 0 + if (state->arg_num < 1 || strlen(arguments->raw_server) == 0 || strlen(arguments->nick) == 0 || arguments->port == 0) argp_usage (state); break; @@ -1505,7 +1542,9 @@ void event_connect (irc_session_t *session, const char *event, const char *origi manage_event(session, event, origin, params, count); + pthread_mutex_lock(&args.mutex); ctx->ready = 1; + pthread_mutex_unlock(&args.mutex); debug("Connected!\n"); for (i = 0; i < ctx->n_channels; i++) { @@ -1621,22 +1660,25 @@ irc_session_t* do_connect() memset (&callbacks, 0, sizeof(callbacks)); callbacks.event_connect = event_connect; - callbacks.event_join = event_join; callbacks.event_nick = event_nick; callbacks.event_quit = event_quit; + callbacks.event_join = event_join; callbacks.event_part = event_part; callbacks.event_mode = manage_event; + callbacks.event_umode = manage_event; callbacks.event_topic = event_topic; callbacks.event_kick = event_kick; callbacks.event_channel = event_channel; callbacks.event_privmsg = event_privmsg; callbacks.event_notice = manage_event; callbacks.event_invite = manage_event; - callbacks.event_umode = manage_event; + callbacks.event_ctcp_req = manage_event; callbacks.event_ctcp_rep = manage_event; callbacks.event_ctcp_action = event_ctcp_action; callbacks.event_unknown = manage_event; callbacks.event_numeric = event_numeric; + callbacks.event_dcc_chat_req = NULL; + callbacks.event_dcc_send_req = NULL; s = irc_create_session (&callbacks); @@ -1645,6 +1687,8 @@ irc_session_t* do_connect() if (!args.with_host) irc_option_set (s, LIBIRC_OPTION_STRIPNICKS); + if(!args.ssl_check) + irc_option_set (s, LIBIRC_OPTION_SSL_NO_VERIFY); irc_set_ctx (s, &args); debug("Connecting to %s port %d with nick %s and password %s...\n", @@ -1661,9 +1705,23 @@ irc_session_t* do_connect() static void* irc_thread (void *arg) { irc_session_t * sp = (irc_session_t *) arg; - // TODO when SSL will be supported - // irc_option_set(sp, LIBIRC_OPTION_SSL_NO_VERIFY); + int ret; irc_run(sp); + ret = irc_errno(sp); + pthread_mutex_lock(&args.mutex); + if (args.ready == 0) { + // we hadn't even connected yet + // TODO2 maybe not all errors are fatal? + info("Connection failed: %s\n", irc_strerror(ret)); + if (ret == LIBIRC_ERR_CONNECT_SSL_FAILED) + info("(Maybe try without --ssl?)\n"); + if (ret == LIBIRC_ERR_SSL_CERT_VERIFY_FAILED) + info("(Try with --no-check-certificate if security is not an issue?)\n"); + } else { + info("Connection interrupted: %s\n", irc_strerror(ret)); + } + args.ready = -ret; + pthread_mutex_unlock(&args.mutex); return 0; } @@ -1741,16 +1799,22 @@ int start (int max_wait) //gettimeofday(&tp1, NULL); - if (!args.ready) { - waiting = 0; + // TODO do things nicely with a cond raised by a connection error, connection + // success, or timeout + waiting = 0; + usleep(200000); + while (1) { + int val; + pthread_mutex_lock(&args.mutex); + val = args.ready; + pthread_mutex_unlock(&args.mutex); + if (val) + break; debug("Waiting for the connection to be ready...\n"); - while (waiting < max_wait && !args.ready) { - debug("waiting (%d)\n", waiting); - sleep(1); - waiting++; - } - if (!args.ready) { - debug("nick is %s\n", args.nick); + debug("waiting (%d)\n", waiting); + sleep(1); + waiting++; + if (waiting >= max_wait) { int new_max_wait = max_wait * args.retry_factor; info("Connection timed out after %d seconds, retrying for %d seconds...\n", max_wait, new_max_wait); @@ -1761,57 +1825,80 @@ int start (int max_wait) } } - debug("[main] Starting loop\n"); - while (cont) { - n_results = pop_fifo_set(&fifos, &results); - debug("[main] I popped %d results\n", n_results); - for (i = 0; i < n_results; i++) { - if (!results[i].line) { - debug("[main] Sentinel seen, we will exit\n"); - cont = 0; // sentinel - } else { - debug("[main] manage line %s %s, pointer %p\n", - results[i].destination, results[i].line, results[i].full_line ); - rsl = do_cmd_msg(s, results[i].destination, results[i].line); - if (rsl > 0) { - error = irc_errno(s); - info("(%d) %s when interpreting: %s", - error, irc_strerror(error), results[i].line); - } - if (rsl < 0) { - // successful exit - cont = 0; + if (args.ready > 0) { + + debug("[main] Starting loop\n"); + while (cont) { + n_results = pop_fifo_set(&fifos, &results); + debug("[main] I popped %d results\n", n_results); + for (i = 0; i < n_results; i++) { + if (!results[i].line) { + debug("[main] Sentinel seen, we will exit\n"); + cont = 0; // sentinel + } else { + debug("[main] manage line %s %s, pointer %p\n", + results[i].destination, results[i].line, results[i].full_line ); + rsl = do_cmd_msg(s, results[i].destination, results[i].line); + if (rsl > 0) { + error = irc_errno(s); + info("(%d) %s when interpreting: %s", + error, irc_strerror(error), results[i].line); + } + if (rsl < 0) { + // successful exit + cont = 0; + } } } - } - // now, we free stuff - for (i = 0; i < n_results; i++) - if (results[i].full_line) - free(results[i].full_line); - free(results); - debug("[main] I managed my lines\n"); - if (cont && !args.ready) { - info("Connection lost, reconnecting...\n"); - return start(max_wait); - } + // now, we free stuff + for (i = 0; i < n_results; i++) + if (results[i].full_line) + free(results[i].full_line); + free(results); + debug("[main] I managed my lines\n"); + int val; + pthread_mutex_lock(&args.mutex); + val = args.ready; + pthread_mutex_unlock(&args.mutex); + if (cont && val <= 0) { + info("Connection lost, reconnecting...\n"); + return start(max_wait); + } - if (args.show_inferred && args.default_destination == DEFAULT_LAST_OUT) - fprintf(stderr, "[%s] ", args.last_chans_out); - - if (cont) - usleep(args.interval); + if (args.show_inferred && args.default_destination == DEFAULT_LAST_OUT) + fprintf(stderr, "[%s] ", args.last_chans_out); - debug("[main] endloop\n"); - } + if (cont) + usleep(args.interval); - debug("[main] exiting\n"); + debug("[main] endloop\n"); + } + debug("[main] exiting\n"); + + usleep(args.interval_after); + } - usleep(args.interval_after); irc_disconnect(s); + free(args.server); + pthread_mutex_destroy(&args.mutex); + return 0; } +void ssl_rename() { + args.server = malloc((strlen(args.raw_server)+2) * sizeof(char*)); + assert(args.server); + if (args.ssl) { + // this is libircclient's convention, I don't really like it + args.server[0] = '#'; + args.server[1] = 0; + } else { + args.server[0] = 0; + } + strcat(args.server, args.raw_server); +} + int main (int argc, char **argv) { @@ -1821,13 +1908,16 @@ int main (int argc, char **argv) // parse command line arguments to set option values argp_parse (&argp, argc, argv, 0, 0, &args); + // setup SSL + ssl_rename(); + // initialize the fifo init_fifo_set(&fifos); saw_user(args.nick); //return test_fifo_set(); - // start trying to connet with the initial retry interval + // start trying to connect with the initial retry interval return start(args.retry_after); }