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);
}