commit 4a0305df5ec381fc60be65da6bca10472823594d
Author: hhvn <dev@hhvn.uk>
Date: Sat, 23 Oct 2021 22:54:51 +0100
INIT: probably should have done this before. Basic UI.
Diffstat:
A | FORMAT | | | 4 | ++++ |
A | Makefile | | | 22 | ++++++++++++++++++++++ |
A | chan.c | | | 132 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.h | | | 151 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | handle.c | | | 351 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | hirc.h | | | 117 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | hist.c | | | 196 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 239 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | nick.c | | | 200 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | serv.c | | | 328 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | struct.h | | | 144 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ui.c | | | 240 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
12 files changed, 2124 insertions(+), 0 deletions(-)
diff --git a/FORMAT b/FORMAT
@@ -0,0 +1,4 @@
+<line> ::= [<timestamp> ' '] [<prefix> ' '] <msg>
+<timestamp> ::= '!' <time>
+<time> ::= (string representation of long long)
+<prefix> ::= ':' <servername> | ':' <nick> ['!' <user>] ['@' <host>]
diff --git a/Makefile b/Makefile
@@ -0,0 +1,22 @@
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+BIN = hirc
+OBJ = main.o handle.o hist.o nick.o chan.o serv.o ui.o
+
+# Comment to disable TLS
+LDTLS = -ltls
+CTLS = -DTLS
+
+CFLAGS = -g -O0 $(CTLS)
+LDFLAGS = -lncurses $(LDTLS)
+
+$(BIN): $(OBJ)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ)
+
+clean:
+ -rm -f $(OBJ)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $<
+
+$(OBJ): config.h
diff --git a/chan.c b/chan.c
@@ -0,0 +1,132 @@
+#include <stdlib.h>
+#include <string.h>
+#include "hirc.h"
+
+void
+chan_free(struct Channel *channel) {
+ if (channel) {
+ free(channel->name);
+ free(channel->mode);
+ nick_free_list(&channel->nicks);
+ free(channel->nicks);
+ hist_free_list(channel->history);
+ free(channel);
+ }
+}
+
+void
+chan_free_list(struct Channel **head) {
+ struct Channel *p;
+
+ if (!head || !*head)
+ return;
+
+ for (p = (*head)->next; p; p = p->next)
+ chan_free(p->prev);
+ *head = NULL;
+}
+
+struct Channel *
+chan_create(struct Server *server, char *name) {
+ struct Channel *channel;
+
+ channel = emalloc(sizeof(struct Channel));
+ channel->name = name ? estrdup(name) : NULL;
+ channel->next = channel->prev = NULL;
+ channel->nicks = NULL;
+ channel->old = 0;
+ channel->server = server;
+ channel->history = emalloc(sizeof(struct HistInfo));
+ channel->history->activity = Activity_ignore;
+ channel->history->unread = 0;
+ channel->history->server = server;
+ channel->history->channel = channel;
+ channel->history->history = NULL;
+
+ return channel;
+}
+
+int
+chan_selected(struct Channel *channel) {
+ if (selected_channel == channel)
+ return 1;
+ else
+ return 0;
+}
+
+struct Channel *
+chan_add(struct Server *server, struct Channel **head, char *name) {
+ struct Channel *channel, *p;
+
+ if (!name)
+ return NULL;
+
+ if ((channel = chan_create(server, name)) == NULL)
+ return NULL;
+
+ if (!*head) {
+ *head = channel;
+ return channel;
+ }
+
+ p = *head;
+ for (; p && p->next; p = p->next);
+ p->next = channel;
+ channel->prev = p;
+
+ return channel;
+}
+
+struct Channel *
+chan_get(struct Channel **head, char *name, int old) {
+ struct Channel *p;
+
+ /* if old is negative, match regardless of p->old
+ * else return only when p->old and old match */
+
+ if (!head || !*head || !name)
+ return NULL;
+
+ for (p = *head; p; p = p->next) {
+ if (strcmp(p->name, name) == 0 && (old < 0 || p->old == old))
+ return p;
+ }
+
+ return NULL;
+}
+
+int
+chan_isold(struct Channel *channel) {
+ if (channel)
+ return channel->old;
+ else
+ return 0;
+}
+
+void
+chan_setold(struct Channel *channel, int old) {
+ channel->old = old;
+}
+
+int
+chan_remove(struct Channel **head, char *name) {
+ struct Channel *p;
+
+ if (!head || !name)
+ return -1;
+
+ if ((p = chan_get(head, name, -1)) == NULL)
+ return 0;
+
+ if (p->prev == NULL) {
+ *head = p->next;
+ chan_free(p);
+ return 1;
+ }
+
+ p->prev->next = p->next;
+ if (p->next != NULL)
+ p->next->prev = p->prev;
+ chan_free(p);
+ return 1;
+}
diff --git a/config.h b/config.h
@@ -0,0 +1,151 @@
+#ifndef H_CONFIG
+#define H_CONFIG
+
+#include "struct.h"
+
+#define MAIN_NICK "hhvn"
+#define MAIN_USERNAME "Fanatic"
+#define MAIN_REALNAME "gopher://hhvn.uk"
+
+static char *logdir = "~/.local/hirc";
+static struct Netconfig netconfig[] = {
+ {
+ .name = "test",
+ .host = "localhost",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = { "#test", "#test2", NULL },
+ .tls = 0,
+ .tls_verify = 0,
+ },
+ /*
+ {
+ .name = "hlircnet",
+ .host = "irc.hhvn.uk",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = {
+ "#hlircnet", "#help", "#gopher", "#vhosts",
+ "#hlfm", "#cgo", "#distrotube", NULL
+ },
+ },
+ {
+ .name = "dataswamp",
+ .host = "irc.dataswamp.org",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = { "#dataswamp", NULL },
+ },
+ {
+ .name = "efnet",
+ .host = "efnet.port80.se",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = { "#asciiart", "#LRH", "#thepiratebay.org", NULL },
+ },
+ {
+ .name = "ikteam",
+ .host = "irc.nk.ax",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = { "#chat", NULL },
+ },
+ {
+ .name = "nebulacentre",
+ .host = "irc.nebulacentre.net",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = { "#general", NULL },
+ },
+ {
+ .name = "sdf",
+ .host = "irc.sdf.org",
+ .port = "6667",
+ .nick = MAIN_NICK,
+ .user = MAIN_USERNAME,
+ .real = MAIN_REALNAME,
+ .join = { "#sdf", "#gopher", "#helpdesk", NULL },
+ }
+ */
+};
+
+/* real maximum = MAX_HISTORY * (channels + servers + queries) */
+#define MAX_HISTORY 1024
+
+static unsigned short colourmap[] = {
+ /* original 16 mirc colours
+ * some clients use the first 16 ansi colours for this,
+ * but here I use the 256 colours to ensure terminal-agnosticism */
+ [0] = 255, 16, 19, 46, 124, 88, 127, 184,
+ [8] = 208, 46, 45, 51, 21, 201, 240, 255,
+
+ /* extended */
+ [16] = 52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
+ [28] = 88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
+ [40] = 124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161,
+ [52] = 196, 208, 226, 154, 46, 86, 51, 75, 21, 171, 201, 198,
+ [64] = 203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205,
+ [76] = 217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212,
+ [88] = 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231,
+
+ /* transparency */
+ [99] = -1
+};
+
+/* (mIRC) colour for any messages sent by oneself */
+static unsigned short selfcolour = 90;
+
+/* (mIRC) inclusive colour range for any messages sent
+ * by others. Use same number twice for constant colour */
+static unsigned short othercolour[2] = {28, 63};
+
+/* default channel types */
+static char *default_chantypes = "#&!+";
+
+/* default prefixes/priveledges, (symbols)modes */
+static char *default_prefixes = "(@+)ov";
+
+/* send ping to server after n seconds of inactivity */
+static int pinginact = 200;
+
+/* max seconds to wait between reconnects */
+static long maxreconnectinterval = 600;
+
+/* number of seconds between reconnects,
+ * multiplied by amount of failed reconnects.
+ * Example: reconnectinterval = 10, maxreconnectinterval = 600
+ * 1st: 0 * 10 = 0
+ * 2nd: 1 * 10 = 10
+ * 3rd: 2 * 10 = 20
+ * 10th: 10 * 10 = 100
+ * 60th: 60 * 10 = 600
+ * 61st: 600, maxreconnectinterval reached */
+static long reconnectinterval = 10;
+
+/* nicklist location:
+ * HIDDEN, LEFT, RIGHT */
+static short nicklistlocation = RIGHT;
+
+/* width of nicklist in columns */
+static int nicklistwidth = 15;
+
+/* window list location:
+ * HIDDEN, LEFT, RIGHT */
+static short winlistlocation = LEFT;
+
+/* width of window list in columns */
+static int winlistwidth = 25;
+
+#endif /* H_CONFIG */
diff --git a/handle.c b/handle.c
@@ -0,0 +1,351 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "hirc.h"
+
+struct Handler handlers[] = {
+ { "PING", handle_PING },
+ { "JOIN", handle_JOIN },
+ { "PART", handle_PART },
+ { "QUIT", handle_QUIT },
+ { "NICK", handle_NICK },
+ { "PRIVMSG", handle_PRIVMSG },
+ { "NOTICE", handle_PRIVMSG },
+ { "005", handle_ISUPPORT },
+ { "353", handle_NAMREPLY },
+ { "433", handle_NICKNAMEINUSE },
+ { NULL, NULL },
+};
+
+void
+handle_PING(char *msg, char **params, struct Server *server, time_t timestamp) {
+ if (**params == ':')
+ params++;
+
+ if (param_len(params) < 2)
+ return;
+
+ ircprintf(server, "PONG :%s\r\n", *(params+1));
+}
+
+void
+chan_printlist(struct Server *server) {
+ struct Channel *channel;
+ struct Nick *nick;
+
+ printf("---\n");
+ for (channel = server->channels; channel; channel = channel->next) {
+ if (chan_isold(channel))
+ printf("%s (old)", channel->name);
+ else
+ printf("%s: ", channel->name);
+ for (nick = channel->nicks; nick; nick = nick->next) {
+ if (nick_isself(nick))
+ printf("%c%s(me) ", nick->priv, nick->nick);
+ else
+ printf("%c%s ", nick->priv, nick->nick);
+ }
+ printf("\n");
+ }
+ for (channel = server->privs; channel; channel = channel->next) {
+ if (chan_isold(channel))
+ printf("%s (old\n)", channel->name);
+ else
+ printf("%s\n", channel->name);
+ }
+ printf("---\n");
+}
+
+void
+handle_JOIN(char *msg, char **params, struct Server *server, time_t timestamp) {
+ struct Channel *chan;
+ struct Nick *nick;
+ char *target;
+
+ if (**params != ':' || param_len(params) < 3)
+ return;
+
+ target = *(params+2);
+ if ((chan = chan_get(&server->channels, target, -1)) == NULL)
+ chan = chan_add(server, &server->channels, target);
+ chan_setold(chan, 0);
+
+ nick = nick_create(*params, ' ', server);
+ if (nick_get(&chan->nicks, nick->nick) == NULL)
+ nick_add(&chan->nicks, *params, ' ', server);
+
+ hist_add(server, server->history, nick, msg, params, Activity_status, timestamp, HIST_LOG);
+ hist_add(server, chan->history, nick, msg, params, Activity_status, timestamp, HIST_SHOW);
+ nick_free(nick);
+
+ chan_printlist(server);
+}
+
+void
+handle_PART(char *msg, char **params, struct Server *server, time_t timestamp) {
+ struct Channel *chan;
+ struct Nick *nick;
+ char *target;
+
+ if (**params != ':' || param_len(params) < 3)
+ return;
+
+ target = *(params+2);
+ if ((chan = chan_get(&server->channels, target, -1)) == NULL)
+ chan = chan_add(server, &server->channels, target);
+
+ nick = nick_create(*params, ' ', server);
+ if (nick_isself(nick)) {
+ chan_setold(chan, 1);
+ nick_free_list(&chan->nicks);
+ } else {
+ nick_remove(&chan->nicks, nick->nick);
+ }
+
+ hist_add(server, server->history, nick, msg, params, Activity_status, timestamp, HIST_LOG);
+ hist_add(server, chan->history, nick, msg, params, Activity_status, timestamp, HIST_SHOW);
+ nick_free(nick);
+
+ chan_printlist(server);
+}
+
+void
+handle_QUIT(char *msg, char **params, struct Server *server, time_t timestamp) {
+ struct Channel *chan;
+ struct Nick *nick;
+
+ if (**params != ':' || param_len(params) < 2)
+ return;
+
+ nick = nick_create(*params, ' ', server);
+ if (nick_isself(nick)) {
+ /* TODO: umm, sound like a big deal anyone? */
+ (void)0;
+ }
+
+ hist_add(server, server->history, nick, msg, params, Activity_status, timestamp, HIST_LOG);
+ for (chan = server->channels; chan; chan = chan->next) {
+ if (nick_get(&chan->nicks, nick->nick) != NULL) {
+ nick_remove(&chan->nicks, nick->nick);
+ hist_add(server, chan->history, nick, msg, params, Activity_status, timestamp, HIST_SHOW);
+ }
+ }
+
+ nick_free(nick);
+
+ chan_printlist(server);
+}
+
+void
+handle_PRIVMSG(char *msg, char **params, struct Server *server, time_t timestamp) {
+ int act_direct = Activity_hilight, act_regular = Activity_message;
+ struct Channel *chan;
+ struct Channel *priv;
+ struct Nick *nick;
+ char *target;
+
+ if (**params != ':' || param_len(params) < 4)
+ return;
+
+ if (strcmp(*params, "NOTICE") == 0)
+ act_direct = act_regular = Activity_notice;
+
+ target = *(params + 2);
+ nick = nick_create(*params, ' ', server);
+ if (strchr(nick->nick, '.')) {
+ /* it's a server */
+ hist_add(server, server->history, NULL, msg, params, Activity_status, timestamp, HIST_DFL);
+ } else if (strcmp(target, server->self->nick) == 0) {
+ /* it's messaging me */
+ if ((priv = chan_get(&server->privs, nick->nick, -1)) == NULL)
+ priv = chan_add(server, &server->privs, nick->nick);
+ chan_setold(priv, 0);
+
+ hist_add(server, priv->history, nick, msg, params, act_direct, timestamp, HIST_DFL);
+ } else if (nick_isself(nick) && !chrcmp(*target, "#&!+")) {
+ /* i'm messaging someone */
+ if ((priv = chan_get(&server->privs, target, -1)) == NULL)
+ priv = chan_add(server, &server->privs, target);
+ chan_setold(priv, 0);
+
+ hist_add(server, priv->history, nick, msg, params, act_regular, timestamp, HIST_DFL);
+ } else {
+ /* message to a channel */
+ if ((chan = chan_get(&server->channels, target, -1)) == NULL)
+ chan = chan_add(server, &server->channels, target);
+
+ hist_add(server, chan->history, nick, msg, params, act_regular, timestamp, HIST_DFL);
+ }
+
+ nick_free(nick);
+ chan_printlist(server);
+ hist_print_server(server);
+}
+
+void
+handle_ISUPPORT(char *msg, char **params, struct Server *server, time_t timestamp) {
+ char *key, *value;
+
+ hist_add(server, server->history, NULL, msg, params, Activity_status, timestamp, HIST_DFL);
+ if (**params == ':')
+ params++;
+
+ if (param_len(params) < 4)
+ return;
+
+ params += 2;
+
+ /* skip the last param ".... :are supported by this server" */
+ for (; *params && *(params+1); params++) {
+ key = *params;
+ if ((value = strchr(key, '=')) != NULL) {
+ *value = '\0';
+ if (*(value+1))
+ value++;
+ else
+ value = NULL;
+ }
+
+ support_set(server, key, value);
+ }
+}
+
+void
+handle_NAMREPLY(char *msg, char **params, struct Server *server, time_t timestamp) {
+ struct Channel *chan;
+ char *nick, priv, *target;
+ char **nicks, **nicksref;
+ char *supportedprivs;
+
+ if (**params == ':')
+ params++;
+
+ if (param_len(params) < 5)
+ return;
+
+ params += 3;
+
+ target = *params;
+ if ((chan = chan_get(&server->channels, target, -1)) == NULL)
+ chan = chan_add(server, &server->channels, target);
+ params++;
+
+ supportedprivs = struntil(support_get(server, "PREFIX"), ')') + 1;
+
+ nicksref = nicks = param_create(*params);
+ for (; *nicks && **nicks; nicks++) {
+ priv = ' ';
+ nick = *nicks;
+ if (chrcmp(**nicks, supportedprivs)) {
+ priv = **nicks;
+ while (chrcmp(*nick, supportedprivs))
+ nick++;
+ }
+ if (nick_get(&chan->nicks, nick) != NULL)
+ nick_remove(&chan->nicks, nick);
+ nick_add(&chan->nicks, nick, priv, server);
+ }
+
+ param_free(nicksref);
+
+ chan_printlist(server);
+}
+
+void
+handle_NICKNAMEINUSE(char *msg, char **params, struct Server *server, time_t timestamp) {
+ char nick[64]; /* should be limited to 9 chars, but newer servers *shrug*/
+
+ hist_add(server, server->history, NULL, msg, params, Activity_status, timestamp, HIST_DFL);
+ snprintf(nick, sizeof(nick), "%s_", server->self->nick);
+ nick_free(server->self);
+ server->self = nick_create(nick, ' ', server);
+ server->self->self = 1;
+ ircprintf(server, "NICK %s\r\n", nick);
+}
+
+void
+handle_NICK(char *msg, char **params, struct Server *server, time_t timestamp) {
+ struct Nick *nick, *chnick;
+ struct Channel *chan;
+ char *newnick;
+
+ if (**params != ':' || !*(params+1) || !*(params+2))
+ return;
+
+ nick = nick_create(*params, ' ', server);
+ hist_add(server, server->history, nick, msg, params, Activity_status, timestamp, HIST_DFL);
+ newnick = *(params+2);
+
+ if (strcmp(nick->nick, newnick) == 0)
+ return;
+
+ if (nick_isself(nick)) {
+ nick_free(server->self);
+ server->self = nick_create(newnick, ' ', server);
+ server->self->self = 1;
+ }
+
+ for (chan = server->channels; chan; chan = chan->next) {
+ if ((chnick = nick_get(&chan->nicks, nick->nick)) != NULL) {
+ nick_add(&chan->nicks, newnick, chnick->priv, server);
+ nick_remove(&chan->nicks, nick->nick);
+ }
+ }
+}
+
+void
+handle(int rfd, struct Server *server) {
+ time_t timestamp;
+ char **params;
+ char *cmd;
+ char *msg;
+ char buf[511];
+ /* using a buffer size of 511:
+ * - RFC1459 defines the maximum size of a message to be 512
+ * - read_line() doesn't copy the \r\n so this is reduced to 510
+ * - a \0 is needed at the end, so 510 + 1 = 511 */
+ int i;
+
+ if (!read_line(rfd, buf, sizeof(buf))) {
+ if (buf[0] == EOF || buf[0] == 3 || buf[0] == 4) {
+ serv_disconnect(server, 1);
+ hist_format(server, server->history, Activity_error, HIST_SHOW,
+ "SELF_CONNECTLOST %s %s %s :EOF received",
+ server->name, server->host, server->port);
+ }
+ return;
+ }
+ msg = buf;
+
+ if (*msg == '!' && strchr(msg, ' ') && *(strchr(msg, ' ')+1) && *(msg+1) != ' ') {
+ msg++;
+ timestamp = (time_t)strtoll(msg, NULL, 10);
+ msg = strchr(msg, ' ') + 1;
+ } else {
+ timestamp = time(NULL);
+ }
+
+ params = param_create(msg);
+ if (!*params) {
+ free(params);
+ return;
+ }
+
+ if (**params == ':' || **params == '|')
+ cmd = *(params + 1);
+ else
+ cmd = *(params);
+
+ for (i=0; cmd && handlers[i].cmd; i++) {
+ if (strcmp(handlers[i].cmd, cmd) == 0) {
+ if (handlers[i].func)
+ handlers[i].func(msg, params, server, timestamp);
+ /* NULL handlers will stop a message being added to server->history */
+ return;
+ }
+ }
+
+ /* add it to server->history if there is no handler */
+ hist_add(server, server->history, NULL, msg, params, Activity_status, timestamp, HIST_DFL);
+}
diff --git a/hirc.h b/hirc.h
@@ -0,0 +1,117 @@
+#ifndef H_HIRC
+#define H_HIRC
+
+#include "struct.h"
+#include "config.h"
+#define PARAM_MAX 64
+
+/* main.c */
+void * emalloc(size_t size);
+char * estrdup(const char *str);
+void param_free(char **params);
+int param_len(char **params);
+char ** param_create(char *msg);
+int read_line(int fd, char *buf, size_t buf_len);
+int ircprintf(struct Server *server, char *format, ...);
+char * homepath(char *path);
+char chrcmp(char c, char *s);
+char * struntil(char *str, char until);
+
+/* chan.c */
+void chan_free(struct Channel *channel);
+void chan_free_list(struct Channel **head);
+struct Channel *chan_create(struct Server *server, char *name);
+struct Channel *chan_get(struct Channel **head, char *name, int old);
+struct Channel *chan_add(struct Server *server, struct Channel **head, char *name);
+int chan_isold(struct Channel *channel);
+void chan_setold(struct Channel *channel, int old);
+/* struct Channel *chan_dup(struct Channel *channel); */
+int chan_remove(struct Channel **head, char *name);
+int chan_selected(struct Channel *channel);
+
+/* nick.c */
+void prefix_tokenize(char *prefix, char **nick, char **ident, char **host);
+void nick_free(struct Nick *nick);
+void nick_free_list(struct Nick **head);
+struct Nick * nick_create(char *prefix, char priv, struct Server *server);
+struct Nick * nick_get(struct Nick **head, char *nick);
+struct Nick * nick_add(struct Nick **head, char *prefix, char priv, struct Server *server);
+struct Nick * nick_dup(struct Nick *nick, struct Server *server);
+int nick_isself(struct Nick *nick);
+int nick_isself_server(struct Nick *nick, struct Server *server);
+int nick_remove(struct Nick **head, char *nick);
+char * nick_strprefix(struct Nick *nick);
+
+/* hist.c */
+void hist_free(struct History *history);
+void hist_free_list(struct HistInfo *histinfo);
+struct History *hist_create(struct Server *server, struct Nick *from, char *msg,
+ char **params, enum Activity activity, time_t timestamp, enum HistOpt options);
+struct History *hist_add(struct Server *server, struct HistInfo *histinfo,
+ struct Nick *from, char *msg, char **params, enum Activity activity,
+ time_t timestamp, enum HistOpt options);
+struct History *hist_format(struct Server *server, struct HistInfo *history,
+ enum Activity activity, enum HistOpt options, char *format, ...);
+int hist_len(struct History **history);
+void hist_print(struct HistInfo *histinfo, char *prefix); /* debug */
+void hist_print_server(struct Server *server); /* debug */
+int hist_log(char *msg, struct Nick *from, time_t timestamp, struct Server *server);
+
+/* serv.c */
+void serv_free(struct Server *server);
+void serv_connect(struct Server *server);
+struct Server * serv_create(char *name, char *host, char *port, char *nick,
+ char *username, char *realname, int tls, int tls_verify);
+struct Server * serv_get(struct Server **head, char *name);
+struct Server * serv_get_byrfd(struct Server **head, int rfd);
+struct Server * serv_add(struct Server **head, char *name, char *host,
+ char *port, char *nick, char *username, char *realname, int tls, int tls_verify);
+int serv_len(struct Server **head);
+int serv_poll(struct Server **head, int timeout);
+int serv_remove(struct Server **head, char *name);
+int serv_selected(struct Server *server);
+void serv_disconnect(struct Server *server, int reconnect);
+char * support_get(struct Server *server, char *key);
+void support_set(struct Server *server, char *key, char *value);
+
+/* handle.c */
+void handle(int rfd, struct Server *server);
+void handle_PING(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_JOIN(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_PART(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_QUIT(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_PRIVMSG(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_ISUPPORT(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_NAMREPLY(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_NICKNAMEINUSE(char *msg, char **params, struct Server *server, time_t timestamp);
+void handle_NICK(char *msg, char **params, struct Server *server, time_t timestamp);
+
+/* ui.c */
+void ui_init(void);
+void ui_read(void);
+void ui_redraw(void);
+void ui_draw_input(void);
+void ui_error_(char *file, int line, char *format, ...);
+#define ui_error(format, ...) ui_error_(__FILE__, __LINE__, format, __VA_ARGS__);
+void ui_perror_(char *file, int line, char *str);
+#define ui_perror(str) ui_perror_(__FILE__, __LINE__, str);
+#ifdef TLS
+#include <tls.h>
+void ui_tls_config_error_(char *file, int line, struct tls_config *config, char *str);
+#define ui_tls_config_error(config, str) ui_tls_config_error_(__FILE__, __LINE__, config, str);
+void ui_tls_error_(char *file, int line, struct tls *ctx, char *str);
+#define ui_tls_error(ctx, str) ui_tls_error_(__FILE__, __LINE__, ctx, str);
+#endif /* TLS */
+
+/* main.c */
+extern struct HistInfo *main_buf;
+
+/* ui.c */
+extern struct Channel *selected_channel;
+extern struct Server *selected_server;
+extern struct Window mainwindow;
+extern struct Window inputwindow;
+extern struct Window nicklist;
+extern struct Window winlist;
+
+#endif /* H_HIRC */
diff --git a/hist.c b/hist.c
@@ -0,0 +1,196 @@
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ncurses.h>
+#include <sys/stat.h>
+#include "hirc.h"
+
+void
+hist_free(struct History *history) {
+ param_free(history->params);
+ if (history->from) {
+ free(history->from->prefix);
+ free(history->from);
+ }
+ free(history->raw);
+ free(history);
+}
+
+void
+hist_free_list(struct HistInfo *histinfo) {
+ struct History *p;
+
+ if (!histinfo->history)
+ return;
+
+ for (p = histinfo->history->next; p; p = p->next)
+ hist_free(p->prev);
+ histinfo->history = NULL;
+}
+
+struct History *
+hist_create(struct Server *server, struct Nick *from, char *msg,
+ char **params, enum Activity activity,
+ time_t timestamp, enum HistOpt options) {
+ struct History *new;
+
+ new = emalloc(sizeof(struct History));
+ new->prev = new->next = NULL;
+ new->timestamp = timestamp ? timestamp : time(NULL);
+ new->activity = activity;
+ new->raw = estrdup(msg);
+ new->params = params;
+ new->options = options;
+ new->origin = server;
+
+ if (from) {
+ new->from = nick_dup(from, server);
+ } else if (**params == ':') {
+ new->from = nick_create(*params, ' ', server);
+ } else {
+ new->from = NULL;
+ }
+
+ return new;
+}
+
+struct History *
+hist_add(struct Server *server, struct HistInfo *histinfo, struct Nick *from,
+ char *msg, char **params, enum Activity activity,
+ time_t timestamp, enum HistOpt options) {
+ struct History *new, *p;
+ int i;
+
+ if (options & HIST_MAIN || activity == Activity_error) {
+ if (histinfo->history != main_buf->history)
+ hist_add(server, main_buf, from, msg, params, activity, timestamp, HIST_SHOW);
+ else if (options & HIST_MAIN)
+ ui_error("HIST_MAIN specified, but history is &main_buf", NULL);
+ }
+
+ new = hist_create(server, from, msg, params, activity, timestamp, options);
+
+ if (histinfo && options & HIST_SHOW && activity > histinfo->activity)
+ histinfo->activity = activity;
+ if (histinfo && options & HIST_SHOW && !chan_selected(histinfo->channel) && !serv_selected(histinfo->server))
+ histinfo->unread++;
+
+ if (!histinfo->history) {
+ histinfo->history = new;
+ return new;
+ }
+
+ for (i=0, p = histinfo->history; p && p->next; p = p->next, i++);
+ if (i == (MAX_HISTORY-1)) {
+ free(p->next);
+ p->next = NULL;
+ }
+
+ new->next = histinfo->history;
+ new->next->prev = new;
+ histinfo->history = new;
+
+
+ // XXX
+ if (options & HIST_SHOW) {
+ wprintw(mainwindow.window, "!%lld :%s %s\n", (long long)timestamp, nick_strprefix(from), msg);
+ refresh();
+ }
+
+ if (options & HIST_LOG) {
+ if (server)
+ hist_log(new->raw, new->from, new->timestamp, server);
+ else
+ ui_error("HIST_LOG specified, but server is NULL", NULL);
+ }
+
+ return new;
+}
+
+struct History *
+hist_format(struct Server *server, struct HistInfo *histinfo, enum Activity activity, enum HistOpt options, char *format, ...) {
+ char msg[1024], **params;
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(msg, sizeof(msg), format, ap);
+ va_end(ap);
+
+ params = param_create(msg);
+
+ return hist_add(server, histinfo, NULL, msg, params, Activity_status, 0, options);
+}
+
+int
+hist_log(char *msg, struct Nick *from, time_t timestamp, struct Server *server) {
+ char filename[2048];
+ int ret, serrno;
+
+ if (*msg == ':' && strchr(msg, ' '))
+ msg = strchr(msg, ' ') + 1;
+
+ if (dprintf(server->logfd, "!%lld :%s %s\n", (long long)timestamp, nick_strprefix(from), msg) < 0) {
+ /* Can't write, try to open the file */
+ snprintf(filename, sizeof(filename), "%s/%s.log", homepath(logdir), server->name);
+ ret = open(filename, O_CREAT|O_APPEND|O_WRONLY);
+ serrno = errno;
+ if (ret == -1 && serrno == ENOENT) {
+ /* No such directory: attempt to create logdir */
+ if (mkdir(homepath(logdir), 0700) == -1) {
+ ui_error("Could not create '%s' directory for logging: %s", logdir, strerror(errno));
+ return -1;
+ }
+ } else if (ret == -1) {
+ ui_error("Could not open '%s' for logging: %s", filename, strerror(serrno));
+ return -1;
+ } else {
+ server->logfd = ret;
+ }
+ } else return 1;
+
+ /* retry */
+ if (dprintf(server->logfd, "!%lld :%s %s\n", (long long)timestamp, nick_strprefix(from), msg) < 0) {
+ ui_error("Failed to write to log of server '%s': %s", server->name, strerror(errno));
+ return -1;
+ }
+
+ return 1;
+}
+
+int
+hist_len(struct History **history) {
+ struct History *p;
+ int i;
+
+ for (i=0, p = *history; p; p = p->next)
+ i++;
+ return i;
+}
+
+void
+hist_print(struct HistInfo *histinfo, char *prefix) { /* debug */
+ struct History *p;
+
+ for (p = histinfo->history; p; p = p->next)
+ printf("%s: %s\n", prefix, p->raw);
+}
+
+void
+hist_print_server(struct Server *server) { /* debug */
+ struct Channel *channel;
+ char prefix[1024];
+
+ hist_print(server->history, server->name);
+ for (channel = server->channels; channel; channel = channel->next) {
+ snprintf(prefix, sizeof(prefix), "%s on %s", channel->name, server->name);
+ hist_print(channel->history, prefix);
+ }
+ for (channel = server->privs; channel; channel = channel->next) {
+ snprintf(prefix, sizeof(prefix), "query with %s on %s", channel->name, server->name);
+ hist_print(channel->history, prefix);
+ }
+}
diff --git a/main.c b/main.c
@@ -0,0 +1,239 @@
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <poll.h>
+#include "hirc.h"
+
+struct Server *servers = NULL;
+struct HistInfo *main_buf;
+
+void *
+emalloc(size_t size) {
+ void *mem;
+
+ if ((mem = malloc(size)) == NULL) {
+ perror("malloc()");
+ exit(EXIT_FAILURE);
+ }
+
+ return mem;
+}
+
+char *
+estrdup(const char *str) {
+ char *ret;
+
+ if ((ret = strdup(str)) == NULL) {
+ perror("strdup()");
+ exit(EXIT_FAILURE);
+ }
+
+ return ret;
+}
+
+void
+param_free(char **params) {
+ char **p;
+
+ for (p = params; p && *p; p++)
+ free(*p);
+ free(params);
+}
+
+int
+param_len(char **params) {
+ int i;
+
+ for (i=0; params && *params; i++, params++);
+ return i;
+}
+
+char **
+param_create(char *msg) {
+ char **ret, **rp;
+ char *params[PARAM_MAX];
+ char tmp[NL_TEXTMAX];
+ char *p, *cur;
+ int final = 0, i;
+
+ for (i=0; i < PARAM_MAX; i++)
+ params[i] = NULL;
+ strlcpy(tmp, msg, sizeof(tmp));
+
+ for (p=cur=tmp, i=0; p && *p && i < PARAM_MAX; p++) {
+ if (!final && *p == ':' && *(p-1) == ' ') {
+ final = 1;
+ *(p-1) = '\0';
+ params[i++] = cur;
+ cur = p + 1;
+ }
+ if (!final && *p == ' ' && *(p+1) != ':') {
+ *p = '\0';
+ params[i++] = cur;
+ cur = p + 1;
+ }
+ }
+ *p = '\0';
+ params[i] = cur;
+
+ ret = emalloc(sizeof(params));
+ for (rp=ret, i=0; params[i] && *params[i]; i++, rp++)
+ *rp = estrdup(params[i]);
+ *rp = NULL;
+
+ return ret;
+}
+
+int
+read_line(int fd, char *buf, size_t buf_len) {
+ size_t i = 0;
+ char c = 0;
+
+ do {
+ if (read(fd, &c, sizeof(char)) != sizeof(char))
+ return 0;
+ if (c != '\r')
+ buf[i++] = c;
+ } while (c != '\n' && i < buf_len);
+ buf[i - 1] = '\0';
+ return 1;
+}
+
+int
+ircprintf(struct Server *server, char *format, ...) {
+ char msg[512];
+ va_list ap;
+ int ret, serrno;
+
+ if (server->status == ConnStatus_notconnected) {
+ ui_error("Not connected to server '%s'", server->name);
+ return -1;
+ }
+
+ va_start(ap, format);
+ if (vsnprintf(msg, sizeof(msg), format, ap) < 0) {
+ va_end(ap);
+ return -1;
+ }
+
+ ret = write(server->wfd, msg, strlen(msg));
+
+ if (ret == -1 && server->status == ConnStatus_connected) {
+ serv_disconnect(server, 1);
+ hist_format(server, server->history, Activity_error, HIST_SHOW,
+ "SELF_CONNECTLOST %s %s %s :%s",
+ server->name, server->host, server->port, strerror(errno));
+ } else if (ret == -1 && server->status != ConnStatus_connecting) {
+ ui_error("Not connected to server '%s'", server->name);
+ }
+
+ va_end(ap);
+ return ret;
+}
+
+char *
+homepath(char *path) {
+ static char ret[1024];
+
+ if (*path == '~') {
+ snprintf(ret, sizeof(ret), "%s/%s", getenv("HOME"), path + 1);
+ return ret;
+ }
+
+ return path;
+}
+
+char
+chrcmp(char c, char *s) {
+ for (; s && *s; s++)
+ if (c == *s)
+ return c;
+
+ return 0;
+}
+
+char *
+struntil(char *str, char until) {
+ static char ret[1024];
+ int i;
+
+ for (i=0; str && *str && i < 1024 && *str != until; i++, str++)
+ ret[i] = *str;
+
+ ret[i] = '\0';
+ return ret;
+}
+
+void
+sighandler(int signal) {
+ return;
+}
+
+int
+main(int argc, char **argv) {
+ struct Server *sp;
+ FILE *file;
+ struct pollfd fds[] = {
+ { .fd = fileno(stdin), .events = POLLIN },
+ };
+
+ main_buf = emalloc(sizeof(struct HistInfo));
+ main_buf->activity = Activity_ignore;
+ main_buf->unread = 0;
+ main_buf->server = NULL;
+ main_buf->channel = NULL;
+ main_buf->history = NULL;
+
+ ui_init();
+ serv_add(&servers, "hlircnet", "irc.hhvn.uk", "6667", "hhvn", "Fanatic", "gopher://hhvn.uk", 1, 0);
+ /* serv_add(&servers, "dataswamp", "127.0.0.1", "6697", "hhvn", "Fanatic", "gopher://hhvn.uk", 1, 0); */
+ for (sp = servers; sp; sp = sp->next)
+ serv_connect(sp);
+
+ for (;;) {
+ if (serv_poll(&servers, 5) < 0) {
+ perror("serv_poll()");
+ exit(EXIT_FAILURE);
+ }
+
+ for (sp = servers; sp; sp = sp->next) {
+ if (sp->rpollfd->revents) {
+ /* received an event */
+ sp->pingsent = 0;
+ sp->lastrecv = time(NULL);
+ sp->rpollfd->revents = 0;
+ handle(sp->rfd, sp);
+ } else if (!sp->pingsent && sp->lastrecv && (time(NULL) - sp->lastrecv) >= pinginact) {
+ /* haven't heard from server in pinginact seconds, sending a ping */
+ ircprintf(sp, "PING :ground control to Major Tom\r\n");
+ sp->pingsent = time(NULL);
+ } else if (sp->pingsent && (time(NULL) - sp->pingsent) >= pinginact) {
+ /* haven't gotten a response in pinginact seconds since
+ * sending ping, this connexion is probably dead now */
+ serv_disconnect(sp, 1);
+ hist_format(sp, sp->history, Activity_error, HIST_SHOW,
+ "SELF_CONNECTLOST %s %s %s :No ping reply in %d seconds",
+ sp->name, sp->host, sp->port, pinginact);
+ } else if (sp->status == ConnStatus_notconnected && sp->reconnect &&
+ (time(NULL) - sp->lastconnected) >= (sp->connectfail * reconnectinterval)) {
+ /* time since last connected is sufficient to initiate reconnect */
+ serv_connect(sp);
+ }
+ }
+
+ ui_read();
+ wrefresh(nicklist.window);
+ wrefresh(winlist.window);
+ wrefresh(mainwindow.window);
+
+ /* always refresh input last */
+ wrefresh(inputwindow.window);
+ }
+
+ return 0;
+}
diff --git a/nick.c b/nick.c
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include "hirc.h"
+
+#define MAX(var1, var2) (var1 > var2 ? var1 : var2)
+#define MIN(var1, var2) (var1 < var2 ? var1 : var2)
+#define MAXA(array) MAX(array[0], array[1])
+#define MINA(array) MIN(array[0], array[1])
+
+unsigned short
+nick_getcolour(char *nick) {
+ unsigned short ret, sum;
+ int i;
+
+ if (othercolour[0] == othercolour[1])
+ return othercolour[0];
+
+ for (sum=i=0; nick && *nick; nick++, i++) {
+ /* don't count certain trailing characters. The following:
+ * hhvn
+ * hhvn_
+ * hhvn2
+ * should all produce the same colour. */
+ if ((*nick == '_' || isdigit(*nick)) && *(nick + 1) == '\0')
+ break;
+
+ sum += *nick * (i + 1);
+ sum ^= *nick;
+ }
+
+ return (sum % (MAXA(othercolour) - MINA(othercolour)) + MINA(othercolour) - 1);
+}
+
+void
+prefix_tokenize(char *prefix, char **nick, char **ident, char **host) {
+ enum { ISNICK, ISIDENT, ISHOST } segment = ISNICK;
+
+ if (*prefix == ':')
+ prefix++;
+
+ if (nick) *nick = prefix;
+ if (ident) *ident = NULL;
+ if (host) *host = NULL;
+
+ for (; prefix && *prefix && segment != ISHOST; prefix++) {
+ if (segment == ISNICK && *prefix == '!') {
+ *prefix = '\0';
+ if (ident)
+ *ident = prefix + 1;
+ segment = ISIDENT;
+ }
+ if (segment == ISIDENT && *prefix == '@') {
+ *prefix = '\0';
+ if (host)
+ *host = prefix + 1;
+ segment = ISHOST;
+ }
+ }
+}
+
+void
+nick_free(struct Nick *nick) {
+ if (nick) {
+ free(nick->prefix);
+ free(nick);
+ }
+}
+
+void
+nick_free_list(struct Nick **head) {
+ struct Nick *p;
+
+ if (!head || !*head)
+ return;
+
+ for (p = (*head)->next; p; p = p->next)
+ nick_free(p->prev);
+ *head = NULL;
+}
+
+struct Nick *
+nick_create(char *prefix, char priv, struct Server *server) {
+ struct Nick *nick;
+
+ if (!prefix || !priv)
+ return NULL;
+
+ nick = emalloc(sizeof(struct Nick));
+ nick->prefix = estrdup(prefix);
+ nick->next = nick->prev = NULL;
+ nick->priv = priv;
+ prefix_tokenize(nick->prefix, &nick->nick, &nick->ident, &nick->host);
+ nick->self = nick_isself_server(nick, server);
+
+ return nick;
+}
+
+int
+nick_isself(struct Nick *nick) {
+ if (!nick)
+ return 0;
+
+ return nick->self;
+}
+
+int
+nick_isself_server(struct Nick *nick, struct Server *server) {
+ if (!nick || !server || !nick->nick)
+ return 0;
+
+ if (strcmp(server->self->nick, nick->nick) == 0)
+ return 1;
+ else
+ return 0;
+}
+
+struct Nick *
+nick_add(struct Nick **head, char *prefix, char priv, struct Server *server) {
+ struct Nick *nick, *p;
+
+ if (!prefix || !priv)
+ return NULL;
+
+ if ((nick = nick_create(prefix, priv, server)) == NULL)
+ return NULL;
+
+ if (!*head) {
+ *head = nick;
+ return nick;
+ }
+
+ p = *head;
+ for (; p && p->next; p = p->next);
+ p->next = nick;
+ nick->prev = p;
+
+ return nick;
+}
+
+struct Nick *
+nick_dup(struct Nick *nick, struct Server *server) {
+ return nick_create(nick->prefix, nick->priv, server);
+}
+
+struct Nick *
+nick_get(struct Nick **head, char *nick) {
+ struct Nick *p;
+
+ p = *head;
+ for (; p; p = p->next) {
+ if (strcmp(p->nick, nick) == 0)
+ return p;
+ }
+
+ return NULL;
+}
+
+int
+nick_remove(struct Nick **head, char *nick) {
+ struct Nick *p;
+
+ if (!head || !nick)
+ return -1;
+
+ if ((p = nick_get(head, nick)) == NULL)
+ return 0;
+
+ if (p->prev == NULL) {
+ *head = p->next;
+ nick_free(p);
+ return 1;
+ }
+
+ p->prev->next = p->next;
+ if (p->next != NULL)
+ p->next->prev = p->prev;
+ nick_free(p);
+ return 1;
+}
+
+char *
+nick_strprefix(struct Nick *nick) {
+ static char ret[1024];
+
+ if (!nick)
+ return NULL;
+
+ if (nick->nick && nick->ident && nick->host)
+ snprintf(ret, sizeof(ret), "%s!%s@%s", nick->nick, nick->ident, nick->host);
+ else if (nick->nick && nick->ident)
+ snprintf(ret, sizeof(ret), "%s!%s", nick->nick, nick->ident);
+ else if (nick->nick)
+ snprintf(ret, sizeof(ret), "%s", nick->nick);
+ else
+ snprintf(ret, sizeof(ret), "");
+
+ return ret;
+}
diff --git a/serv.c b/serv.c
@@ -0,0 +1,328 @@
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <poll.h>
+#ifdef TLS
+#include <tls.h>
+#endif /* TLS */
+#include "hirc.h"
+
+void
+serv_free(struct Server *server) {
+ struct Support *p;
+
+ if (!server)
+ return;
+
+ free(server->name);
+ free(server->username);
+ free(server->realname);
+ free(server->host);
+ free(server->port);
+ free(server->rpollfd);
+ nick_free(server->self);
+ hist_free_list(server->history);
+ chan_free_list(&server->channels);
+ chan_free_list(&server->privs);
+ for (p = server->supports; p; p = p->next) {
+ free(p->prev);
+ free(p->key);
+ free(p->value);
+ }
+#ifdef TLS
+ if (server->tls)
+ tls_free(server->tls_ctx);
+#endif /* TLS */
+ free(p);
+}
+
+struct Server *
+serv_create(char *name, char *host, char *port, char *nick,
+ char *username, char *realname, int tls, int tls_verify) {
+ struct Server *server;
+ struct tls_config *conf;
+
+ if (!name || !host || !port || !nick)
+ return NULL;
+
+ server = emalloc(sizeof(struct Server));
+ server->prev = server->next = NULL;
+ server->wfd = server->rfd = server->logfd = -1;
+ server->rpollfd = emalloc(sizeof(struct pollfd));
+ server->rpollfd->fd = -1;
+ server->rpollfd->events = POLLIN;
+ server->rpollfd->revents = 0;
+ server->status = ConnStatus_notconnected;
+ server->name = estrdup(name);
+ server->username = username ? estrdup(username) : NULL;
+ server->realname = realname ? estrdup(realname) : NULL;
+ server->host = estrdup(host);
+ server->port = estrdup(port);
+ server->supports = NULL;
+ support_set(server, "CHANTYPES", default_chantypes);
+ support_set(server, "PREFIX", default_prefixes);
+ server->self = nick_create(nick, ' ', NULL);
+ server->self->self = 1;
+ server->history = emalloc(sizeof(struct HistInfo));
+ server->history->activity = Activity_ignore;
+ server->history->unread = 0;
+ server->history->server = server;
+ server->history->channel = NULL;
+ server->history->history = NULL;
+ server->channels = NULL;
+ server->privs = NULL;
+ server->reconnect = 0;
+ server->connectfail = 0;
+ server->lastconnected = server->lastrecv = server->pingsent = 0;
+
+#ifdef TLS
+ server->tls = tls;
+ server->tls_ctx = NULL;
+ if (server->tls && (conf = tls_config_new()) == NULL) {
+ ui_tls_config_error(conf, "tls_config_new()");
+ server->tls = 0;
+ }
+
+ if (server->tls && !tls_verify) {
+ tls_config_insecure_noverifycert(conf);
+ tls_config_insecure_noverifyname(conf);
+ }
+
+ if (server->tls && (server->tls_ctx = tls_client()) == NULL) {
+ ui_perror("tls_client()");
+ server->tls = 0;
+ }
+
+ if (server->tls && tls_configure(server->tls_ctx, conf) == -1) {
+ ui_tls_error(server->tls_ctx, "tls_configure()");
+ server->tls = 0;
+ }
+
+ tls_config_free(conf);
+#else
+ if (tls)
+ hist_format(server, server->history, Activity_error, HIST_SHOW,
+ "SELF_TLSNOTCOMPILED %s", server->name);
+#endif /* TLS */
+
+ return server;
+}
+
+struct Server *
+serv_add(struct Server **head, char *name, char *host, char *port,
+ char *nick, char *username, char *realname, int tls, int tls_verify) {
+ struct Server *new, *p;
+
+ if ((new = serv_create(name, host, port, nick, username, realname, tls, tls_verify)) == NULL)
+ return NULL;
+
+ if (!*head) {
+ *head = new;
+ return new;
+ }
+
+ p = *head;
+ for (; p && p->next; p = p->next);
+ p->next = new;
+ new->prev = p;
+
+ return new;
+}
+
+struct Server *
+serv_get(struct Server **head, char *name) {
+ struct Server *p;
+
+ if (!head || !*head || !name)
+ return NULL;
+
+ for (p = *head; p; p = p->next) {
+ if (strcmp(p->name, name) == 0)
+ return p;
+ }
+
+ return NULL;
+}
+
+struct Server *
+serv_get_byrfd(struct Server **head, int rfd) {
+ struct Server *p;
+
+ if (!head || !*head)
+ return NULL;
+
+ for (p = *head; p; p = p->next) {
+ if (p->rfd == rfd)
+ return p;
+ }
+
+ return NULL;
+}
+
+int
+serv_remove(struct Server **head, char *name) {
+ struct Server *p;
+
+ if (!head || !name)
+ return -1;
+
+ if ((p = serv_get(head, name)) == NULL)
+ return 0;
+
+ if (p->prev = NULL) {
+ *head = p->next;
+ serv_free(p);
+ return 1;
+ }
+
+ p->prev->next = p->next;
+ if (p->next != NULL)
+ p->next->prev = p->prev;
+ serv_free(p);
+ return 1;
+}
+
+void
+serv_connect(struct Server *server) {
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ int fd, ret, serrno;
+
+ server->status = ConnStatus_connecting;
+ hist_format(server, server->history, Activity_status, HIST_SHOW|HIST_MAIN,
+ "SELF_CONNECTING %s %s", server->host, server->port);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ((ret = getaddrinfo(server->host, server->port, &hints, &ai)) != 0 || ai == NULL) {
+ hist_format(server, server->history, Activity_error, HIST_SHOW,
+ "SELF_LOOKUPFAIL %s %s %s :%s",
+ server->name, server->host, server->port, gai_strerror(ret));
+ goto fail;
+ }
+ if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1 || connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+ hist_format(server, server->history, Activity_error, HIST_SHOW,
+ "SELF_CONNECTFAIL %s %s %s :%s",
+ server->name, server->host, server->port, strerror(errno));
+ goto fail;
+ }
+
+ server->connectfail = 0;
+ server->status = ConnStatus_connected;
+ server->rfd = server->wfd = fd;
+ hist_format(server, server->history, Activity_status, HIST_SHOW|HIST_MAIN,
+ "SELF_CONNECTED %s %s %s", server->name, server->host, server->port);
+ freeaddrinfo(ai);
+
+ ircprintf(server, "NICK %s\r\n", server->self->nick);
+ ircprintf(server, "USER %s * * :%s\r\n",
+ server->username ? server->username : server->self->nick,
+ server->realname ? server->realname : server->self->nick);
+
+ return;
+
+fail:
+ serv_disconnect(server, 1);
+ if (server->connectfail * reconnectinterval < maxreconnectinterval)
+ server->connectfail += 1;
+ freeaddrinfo(ai);
+}
+
+int
+serv_len(struct Server **head) {
+ struct Server *p;
+ int i;
+
+ for (p = *head, i = 0; p; p = p->next)
+ i++;
+ return i;
+}
+
+int
+serv_poll(struct Server **head, int timeout) {
+ struct pollfd fds[64];
+ struct Server *sp;
+ int i, ret;
+
+ for (i=0, sp = *head; sp; sp = sp->next, i++) {
+ sp->rpollfd->fd = sp->rfd;
+ fds[i].fd = sp->rpollfd->fd;
+ fds[i].events = POLLIN;
+ }
+
+ ret = poll(fds, serv_len(head), timeout);
+ if (errno == EINTR) /* ncurses issue */
+ ret = 0;
+
+ for (i=0, sp = *head; sp; sp = sp->next, i++)
+ if (sp->status == ConnStatus_connecting
+ || sp->status == ConnStatus_connected)
+ sp->rpollfd->revents = fds[i].revents;
+
+ return ret;
+}
+
+void
+serv_disconnect(struct Server *server, int reconnect) {
+ shutdown(server->rfd, SHUT_RDWR);
+ shutdown(server->wfd, SHUT_RDWR);
+ close(server->rfd);
+ close(server->wfd);
+
+ server->rfd = server->wfd = server->rpollfd->fd = -1;
+ server->status = ConnStatus_notconnected;
+ server->lastrecv = server->pingsent = 0;
+ server->lastconnected = time(NULL);
+ server->reconnect = reconnect;
+}
+
+int
+serv_selected(struct Server *server) {
+ if (!selected_channel && selected_server == server)
+ return 1;
+ else
+ return 0;
+}
+
+char *
+support_get(struct Server *server, char *key) {
+ struct Support *p;
+ for (p = server->supports; p; p = p->next)
+ if (strcmp(p->key, key) == 0)
+ return p->value;
+
+ return NULL;
+}
+
+void
+support_set(struct Server *server, char *key, char *value) {
+ struct Support *p;
+
+ if (!server->supports) {
+ server->supports = malloc(sizeof(struct Support));
+ server->supports->prev = server->supports->next = NULL;
+ server->supports->key = key ? strdup(key) : NULL;
+ server->supports->value = value ? strdup(value) : NULL;
+ return;
+ }
+
+ for (p = server->supports; p && p->next; p = p->next) {
+ if (strcmp(p->key, key) == 0) {
+ free(p->value);
+ p->value = strdup(value);
+ return;
+ }
+ }
+
+ p->next = malloc(sizeof(struct Support));
+ p->next->prev = p;
+ p->next->next = NULL;
+ p->next->key = key ? strdup(key) : NULL;
+ p->next->value = value ? strdup(value) : NULL;
+}
diff --git a/struct.h b/struct.h
@@ -0,0 +1,144 @@
+#ifndef H_STRUCT
+#define H_STRUCT
+
+#include <time.h>
+#include <sys/time.h>
+#include <poll.h>
+
+struct Nick {
+ struct Nick *prev;
+ char priv; /* [~&@%+ ] */
+ char *prefix; /* don't read from this, nick, ident, host are
+ pointers to segments of this.
+ printf(":%s!%s@%s\n", nick, ident, host); */
+ char *nick;
+ char *ident;
+ char *host;
+ int self;
+ struct Nick *next;
+};
+
+enum Activity {
+ Activity_ignore,
+ Activity_status,
+ Activity_notice = Activity_status,
+ Activity_error,
+ Activity_message,
+ Activity_hilight,
+};
+
+enum HistOpt {
+ HIST_SHOW = 1, /* show in buffer */
+ HIST_LOG = 2, /* log to server->logfd */
+ HIST_MAIN = 4, /* copy to &main_buf */
+ HIST_DFL = HIST_SHOW|HIST_LOG
+};
+
+struct History {
+ struct History *prev;
+ time_t timestamp;
+ enum Activity activity;
+ enum HistOpt options;
+ char *raw;
+ char **params;
+ struct Server *origin;
+ struct Nick *from;
+ struct History *next;
+};
+
+struct HistInfo {
+ enum Activity activity;
+ int unread;
+ struct Server *server;
+ struct Channel *channel;
+ struct History *history;
+};
+
+struct Channel {
+ struct Channel *prev;
+ int old; /* are we actually in this channel,
+ or just keeping it for memory */
+ char *name;
+ char *mode;
+ char *topic;
+ struct Nick *nicks;
+ struct HistInfo *history;
+ struct Server *server;
+ struct Channel *next;
+};
+
+enum ConnStatus {
+ ConnStatus_notconnected,
+ ConnStatus_connecting,
+ ConnStatus_connected,
+ ConnStatus_file,
+};
+
+struct Support {
+ struct Support *prev;
+ char *key;
+ char *value;
+ struct Support *next;
+};
+
+struct Server {
+ struct Server *prev;
+ int wfd;
+ int rfd;
+ struct pollfd *rpollfd;
+ int logfd;
+ enum ConnStatus status;
+ char *name;
+ char *username;
+ char *realname;
+ char *host;
+ char *port;
+ struct Support *supports;
+ struct Nick *self;
+ struct HistInfo *history;
+ struct Channel *channels;
+ struct Channel *privs;
+ int reconnect;
+ int connectfail; /* number of failed connections */
+ time_t lastconnected; /* last time a connection was lost */
+ time_t lastrecv; /* last time a message was received from server */
+ time_t pingsent; /* last time a ping was sent to server */
+#ifdef TLS
+ int tls;
+ struct tls *tls_ctx;
+#endif /* TLS */
+ struct Server *next;
+};
+
+struct Handler {
+ char *cmd; /* or numeric */
+ void (*func)(char *msg, char **params, struct Server *server, time_t timestamp);
+};
+
+struct Netconfig {
+ char *name;
+ char *host;
+ char *port;
+ char *nick;
+ char *user;
+ char *real;
+ char *join[64];
+ int tls;
+ int tls_verify;
+};
+
+enum WindowLocation {
+ HIDDEN,
+ LEFT,
+ RIGHT,
+};
+
+#include <ncurses.h>
+struct Window {
+ int x, y;
+ int h, w;
+ enum WindowLocation location;
+ WINDOW *window;
+};
+
+#endif /* H_STRUCT */
diff --git a/ui.c b/ui.c
@@ -0,0 +1,240 @@
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ncurses.h>
+#ifdef TLS
+#include <tls.h>
+#endif /* TLS */
+#include "hirc.h"
+
+struct HistInfo *error_buf;
+struct Window mainwindow;
+struct Window inputwindow;
+struct Window nicklist;
+struct Window winlist;
+
+struct Channel *selected_channel = NULL;
+struct Server *selected_server = NULL;
+
+struct {
+ char string[8192];
+ unsigned counter;
+} input;
+
+void
+ui_error_(char *file, int line, char *format, ...) {
+ char msg[1024];
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(msg, sizeof(msg), format, ap);
+ va_end(ap);
+
+ hist_format(NULL, error_buf, Activity_error, HIST_SHOW,
+ "SELF_ERROR %s %d :%s",
+ file, line, msg);
+}
+
+void
+ui_perror_(char *file, int line, char *str) {
+ hist_format(NULL, error_buf, Activity_error, HIST_SHOW,
+ "SELF_ERROR %s %d :%s: %s",
+ file, line, str, strerror(errno));
+}
+
+#ifdef TLS
+void
+ui_tls_config_error_(char *file, int line, struct tls_config *config, char *str) {
+ hist_format(NULL, error_buf, Activity_error, HIST_SHOW,
+ "SELF_ERROR %s %d :%s: %s",
+ file, line, str, tls_config_error(config));
+}
+
+void
+ui_tls_error_(char *file, int line, struct tls *ctx, char *str) {
+ hist_format(NULL, error_buf, Activity_error, HIST_SHOW,
+ "SELF_ERROR %s %d :%s: %s",
+ file, line, str, tls_error(ctx));
+}
+#endif /* TLS */
+
+void
+ui_init(void) {
+ initscr();
+ raw();
+ noecho();
+ nodelay(stdscr, TRUE);
+ keypad(stdscr, TRUE);
+
+ memset(input.string, '\0', sizeof(input.string));
+ input.counter = 0;
+
+ error_buf = emalloc(sizeof(struct HistInfo));
+ error_buf->activity = Activity_ignore;
+ error_buf->unread = 0;
+ error_buf->server = NULL;
+ error_buf->channel = NULL;
+ error_buf->history = NULL;
+
+ if (nicklistlocation != 0 && nicklistlocation == winlistlocation) {
+ ui_error("nicklist and winlist can't be set to same location in config.h", NULL);
+ winlist.location = LEFT;
+ nicklist.location = RIGHT;
+ } else {
+ winlist.location = winlistlocation;
+ nicklist.location = nicklistlocation;
+ }
+
+ mainwindow.window = newwin(0, 0, 0, 0);
+ inputwindow.window = newwin(0, 0, 0, 0);
+ mainwindow.location = -1;
+ inputwindow.location = -1;
+ if (nicklist.location)
+ nicklist.window = newwin(0, 0, 0, 0);
+ if (winlist.location)
+ winlist.window = newwin(0, 0, 0, 0);
+
+ ui_redraw();
+
+ wprintw(nicklist.window, "nicklist");
+ wprintw(winlist.window, "winlist");
+}
+
+void
+ui_placewindow(struct Window *window) {
+ if (window->location != HIDDEN) {
+ wresize(window->window, window->h, window->w);
+ mvwin(window->window, window->y, window->x);
+ wrefresh(window->window);
+ }
+}
+
+void
+ui_read(void) {
+ int key;
+
+ switch (key = wgetch(stdscr)) {
+ case KEY_RESIZE:
+ ui_redraw();
+ break;
+ case KEY_BACKSPACE:
+ if (input.counter) {
+ memmove(&input.string[input.counter - 1],
+ &input.string[input.counter],
+ strlen(&input.string[input.counter]) + 1);
+ input.counter--;
+ ui_draw_input();
+ }
+ break;
+ case KEY_LEFT:
+ if (input.counter) {
+ input.counter--;
+ ui_draw_input();
+ }
+ break;
+ case KEY_RIGHT:
+ if (input.string[input.counter]) {
+ input.counter++;
+ ui_draw_input();
+ }
+ break;
+ case '\n':
+ if (strcmp(input.string, "/quit") == 0) {
+ endwin();
+ exit(0);
+ }
+ wprintw(mainwindow.window, "%s\n", input.string);
+ memset(input.string, '\0', sizeof(input.string));
+ input.counter = 0;
+ ui_draw_input();
+ break;
+ default:
+ if (isprint(key) || iscntrl(key)) {
+ memmove(&input.string[input.counter + 1],
+ &input.string[input.counter],
+ strlen(&input.string[input.counter]));
+ input.string[input.counter++] = key;
+ ui_draw_input();
+ }
+ }
+}
+
+void
+ui_redraw(void) {
+ int x = 0, rx = 0;
+
+ if (winlist.location == LEFT) {
+ winlist.x = winlist.y = 0;
+ winlist.h = LINES;
+ winlist.w = winlistwidth;
+ x = winlist.w + 1;
+ }
+ if (nicklist.location == LEFT) {
+ nicklist.x = winlist.y = 0;
+ nicklist.h = LINES;
+ nicklist.w = nicklistwidth;
+ x = nicklist.w + 1;
+ }
+ if (winlist.location == RIGHT) {
+ winlist.x = COLS - winlistwidth;
+ winlist.y = 0;
+ winlist.h = LINES;
+ winlist.w = winlistwidth;
+ rx = winlistwidth + 1;
+ }
+ if (nicklist.location == RIGHT) {
+ nicklist.x = COLS - nicklistwidth;
+ nicklist.y = 0;
+ nicklist.h = LINES;
+ nicklist.w = nicklistwidth;
+ rx = nicklistwidth + 1;
+ }
+
+ mainwindow.x = x;
+ mainwindow.y = 0;
+ mainwindow.h = LINES - 2;
+ mainwindow.w = COLS - x - rx;
+
+ inputwindow.x = x;
+ inputwindow.y = LINES - 1;
+ inputwindow.h = 1;
+ inputwindow.w = COLS - x - rx;
+
+ ui_placewindow(&nicklist);
+ ui_placewindow(&winlist);
+ ui_placewindow(&mainwindow);
+ ui_placewindow(&inputwindow);
+
+ if (x)
+ mvvline(0, x - 1, '|', LINES);
+ if (rx)
+ mvvline(0, COLS - rx, '|', LINES);
+
+ mvhline(LINES - 2, x, '-', COLS - x - rx);
+
+ ui_draw_input();
+}
+
+void
+ui_draw_input(void) {
+ char *p;
+ char tmp;
+ int offset;
+
+ wclear(inputwindow.window);
+ /* Round input.counter down to the nearest inputwindow.w.
+ * This gives "pages" that are each as long as the width of the input window */
+ offset = ((int) input.counter / inputwindow.w) * inputwindow.w;
+ for (p = input.string + offset; p && *p; p++) {
+ if (iscntrl(*p)) {
+ /* adding 64 will turn ^C into C */
+ tmp = *p + 64;
+ wattron(inputwindow.window, A_REVERSE);
+ waddnstr(inputwindow.window, &tmp, 1);
+ wattroff(inputwindow.window, A_REVERSE);
+ } else waddnstr(inputwindow.window, p, 1);
+ }
+ wmove(inputwindow.window, 0, input.counter - offset);
+}