bip/src/irc.c

3042 lines
76 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* $Id: irc.c,v 1.156 2005/04/21 06:58:50 nohar Exp $
*
* This file is part of the bip project
* Copyright (C) 2004,2005 Arnaud Cornet
* Copyright (C) 2004,2005,2022 Loïc Gomez
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* See the file "COPYING" for the exact licensing terms.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "util.h"
#include "irc.h"
#include "bip.h"
#include "log.h"
#include "connection.h"
#include "md5.h"
#include "utils/base64.h"
// TODO resolve assuming signed overflow does not occur when changing X +- C1
// cmp C2 to X cmp C2 -+ C1
#pragma GCC diagnostic ignored "-Wstrict-overflow"
#define S_CONN_DELAY (10)
extern int sighup;
extern bip_t *_bip;
static int irc_join(struct link_server *server, struct line *line);
static int irc_part(struct link_server *server, struct line *line);
static int irc_mode(struct link_server *server, struct line *line);
static int irc_mode_channel(struct link_server *s, struct channel *channel,
struct line *line, const char *mode, int add,
int cur_arg);
static int irc_kick(struct link_server *server, struct line *line);
static int irc_privmsg(struct link_server *server, struct line *line);
static int irc_notice(struct link_server *server, struct line *line);
static int irc_quit(struct link_server *server, struct line *line);
static int irc_nick(struct link_server *server, struct line *line);
static int irc_generic_quit(struct link_server *server, struct line *line);
static int irc_topic(struct link_server *server, struct line *line);
static int irc_332(struct link_server *server, struct line *line);
static int irc_333(struct link_server *server, struct line *line);
static int irc_353(struct link_server *server, struct line *line);
static int irc_366(struct link_server *server, struct line *line);
static int irc_367(struct link_server *server, struct line *l);
static int irc_368(struct link_server *server, struct line *l);
void irc_server_shutdown(struct link_server *s);
static int origin_is_me(struct line *l, struct link_server *server);
static void ls_set_nick(struct link_server *ircs, char *nick);
static void server_set_chanmodes(struct link_server *l, const char *chanmodes);
static void server_set_prefix(struct link_server *l, const char *prefix);
static void server_init_modes(struct link_server *s);
static int bip_get_index(const char *str, char car);
static int bip_fls(long v);
void oidentd_dump(bip_t *bip);
void irc_client_free(struct link_client *cli);
extern int conf_log_sync_interval;
extern int conf_reconn_timer;
void write_user_list(connection_t *c, char *dest);
static void irc_copy_cli(struct link_client *src, struct link_client *dest,
struct line *line);
static void irc_cli_make_join(struct link_client *ic);
static void server_setup_reconnect_timer(struct link *link);
int irc_cli_bip(bip_t *bip, struct link_client *ic, struct line *line);
static int irc_server_sasl_authenticate(struct link_server *ircs);
static char *sasl_mechanism_to_text(int sasl_mechanism);
#define LAGOUT_TIME 480
#define LAGCHECK_TIME (90)
#define RECONN_TIMER_MAX (600)
#define LOGGING_TIMEOUT (360)
#define CONN_INTERVAL 60
#define CONNECT_TIMEOUT 60
struct channel *channel_new(const char *name)
{
struct channel *chan;
chan = bip_calloc(sizeof(struct channel), (size_t)1);
chan->name = bip_strdup(name);
hash_init(&chan->ovmasks, HASH_NOCASE);
return chan;
}
char *nick_from_ircmask(const char *mask)
{
const char *nick = mask;
char *ret;
size_t len;
if (!mask)
return NULL;
while (*nick && *nick != '!')
nick++;
if (!*nick)
return bip_strdup(mask);
len = (size_t)(nick - mask); // cannot be < 0
ret = bip_malloc(len + 1);
memcpy(ret, mask, len);
ret[len] = 0;
return ret;
}
#define NAMESIZE 256
list_t *channel_name_list(struct link_server *server, struct channel *c)
{
list_t *ret;
hash_iterator_t hi;
size_t len = 0;
char *str = bip_malloc((size_t)(NAMESIZE + 1));
ret = list_new(NULL);
*str = 0;
for (hash_it_init(&c->ovmasks, &hi); hash_it_key(&hi);
hash_it_next(&hi)) {
const char *nick = hash_it_key(&hi);
long int ovmask = (long int)hash_it_item(&hi);
assert(strlen(nick) + 2 < NAMESIZE);
if (len + strlen(nick) + 2 + (ovmask ? 1 : 0) >= NAMESIZE) {
list_add_last(ret, str);
str = bip_malloc((size_t)(NAMESIZE + 1));
*str = 0;
len = 0;
}
if (len != 0) {
strcat(str, " ");
len++;
}
// prepend symbol corresponding to the usermode
int msb;
if ((msb = bip_fls(ovmask))) {
str[len] = server->prefixes[msb - 1];
str[++len] = 0;
}
strcat(str, nick);
len += strlen(nick);
assert(len < NAMESIZE);
}
list_add_last(ret, str);
return ret;
}
char *link_name(struct link_any *l)
{
if (LINK(l))
return LINK(l)->name ? LINK(l)->name : "(null)";
return "*connecting*";
}
static int irc_001(struct link_server *server, struct line *line)
{
(void)line;
if (LINK(server)->s_state == IRCS_WAS_CONNECTED)
LINK(server)->s_state = IRCS_RECONNECTING;
else
LINK(server)->s_state = IRCS_CONNECTING;
/* change nick on client */
unsigned int i;
for (i = 0; i < LINK(server)->l_clientc; i++) {
struct link_client *c = LINK(server)->l_clientv[i];
WRITE_LINE1(CONN(c), LINK(server)->cli_nick, "NICK",
server->nick);
}
return OK_COPY;
}
void irc_start_lagtest(struct link_server *l)
{
l->laginit_ts = time(NULL);
write_line_fast(CONN(l), "PING :" S_PING "\r\n");
}
/*
* returns 0 if we ping timeout
*/
void irc_compute_lag(struct link_server *is)
{
time_t lag;
assert(is->laginit_ts != -1);
lag = time(NULL) - is->laginit_ts;
if (lag > LAGOUT_TIME * 2)
is->lag = LAGOUT_TIME * 2;
else
is->lag = (unsigned)lag;
}
int irc_lags_out(struct link_server *is)
{
if (is->lag > LAGOUT_TIME) {
mylog(LOG_ERROR, "[%s] Lags out! closing", LINK(is)->name);
return 1;
} else {
mylog(LOG_DEBUG, "[%s] lag : %d\n", LINK(is)->name, is->lag);
return 0;
}
}
void irc_lag_init(struct link_server *is)
{
is->lagtest_timeout = LAGCHECK_TIME;
is->laginit_ts = -1;
}
static void irc_server_join(struct link_server *s)
{
list_iterator_t it;
for (list_it_init(&LINK(s)->chan_infos_order, &it); list_it_item(&it);
list_it_next(&it)) {
struct chan_info *ci = list_it_item(&it);
if (!ci->key)
WRITE_LINE1(CONN(s), NULL, "JOIN", ci->name);
else
WRITE_LINE2(CONN(s), NULL, "JOIN", ci->name, ci->key);
}
}
static void irc_server_connected(struct link_server *server)
{
unsigned int i;
LINK(server)->s_state = IRCS_CONNECTED;
LINK(server)->s_conn_attempt = 0;
mylog(LOG_INFO, "[%s] Connected for user %s", LINK(server)->name,
LINK(server)->user->name);
irc_server_join(server);
log_connected(LINK(server)->log);
if (LINK(server)->cli_nick) {
/* we change nick on client */
for (i = 0; i < LINK(server)->l_clientc; i++) {
struct link_client *ic = LINK(server)->l_clientv[i];
WRITE_LINE1(CONN(ic), LINK(server)->cli_nick, "NICK",
server->nick);
}
free(LINK(server)->cli_nick);
LINK(server)->cli_nick = NULL;
}
/* basic helper for nickserv and co */
list_iterator_t itocs;
for (list_it_init(&LINK(server)->on_connect_send, &itocs);
list_it_item(&itocs); list_it_next(&itocs)) {
size_t len = strlen(list_it_item(&itocs)) + 2;
char *str = bip_malloc(len + 1);
sprintf(str, "%s\r\n", (char *)list_it_item(&itocs));
write_line(CONN(server), str);
free(str);
}
if (LINK(server)->l_clientc == 0) {
if (LINK(server)->away_nick)
WRITE_LINE1(CONN(server), NULL, "NICK",
LINK(server)->away_nick);
if (LINK(server)->no_client_away_msg)
WRITE_LINE1(CONN(server), NULL, "AWAY",
LINK(server)->no_client_away_msg);
}
}
/*
* Given the way irc nets disrespect the rfc, we completely forget
* about this damn ircmask...
:irc.iiens.net 352 pwet * ~a je.suis.t1r.net irc.iiens.net pwet H :0 d
-> nohar!~nohar@haruka.t1r.net
*/
static int irc_352(struct link_server *server, struct line *line)
{
(void)server;
if (!irc_line_includes(line, 6))
return ERR_PROTOCOL;
#if 0
if (irc_line_elem_case_equals(line, 6, server->nick)) {
const char *nick = server->nick;
const char *iname = irc_line_elem(line, 3);
const char *ihost = irc_line_elem(line, 4);
char *ircmask = bip_malloc(strlen(nick) + strlen(iname) +
strlen(ihost) + 3);
strcpy(ircmask, nick);
strcat(ircmask, "!");
strcat(ircmask, iname);
strcat(ircmask, "@");
strcat(ircmask, ihost);
if (server->ircmask)
free(server->ircmask);
server->ircmask = ircmask;
}
#endif
#if 0
if (!origin_is_me(line, server)) {
struct channel *channel;
struct nick *nick;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
if (!channel)
return OK_COPY_WHO;
nick = hash_get(&channel->nicks, irc_line_elem(line, 6));
if (!nick)
return OK_COPY_WHO;
}
#endif
return OK_COPY_WHO;
}
static int irc_315(struct link_server *server, struct line *l)
{
(void)l;
struct link *link = LINK(server);
if (link->who_client) {
if (link->who_client->who_count == 0) {
mylog(LOG_DEBUG, "Spurious irc_315");
return OK_COPY_WHO;
}
link->who_client->whoc_tstamp = time(NULL);
if (link->who_client->who_count > 0) {
--link->who_client->who_count;
mylog(LOG_DEBUG,
"RPL_ENDOFWHO: "
"Decrementing who count for %p: %d",
link->who_client, link->who_client->who_count);
}
}
return OK_COPY_WHO;
}
void rotate_who_client(struct link *link)
{
unsigned int i;
mylog(LOG_DEBUG, "rotate_who_client %p", link->who_client);
/* find a client with non-null who_count */
link->who_client = NULL;
for (i = 0; i < link->l_clientc; i++) {
struct link_client *ic = link->l_clientv[i];
if (!list_is_empty(&ic->who_queue)) {
char *l;
while ((l = list_remove_first(&ic->who_queue))) {
write_line(CONN(link->l_server), l);
free(l);
}
link->who_client = ic;
break;
}
}
}
int irc_dispatch_server(bip_t *bip, struct link_server *server,
struct line *line)
{
int ret = OK_COPY;
/* shut gcc up */
(void)bip;
if (!irc_line_includes(line, 0))
return ERR_PROTOCOL;
if (irc_line_elem_equals(line, 0, "PING")) {
if (!irc_line_includes(line, 1))
return ERR_PROTOCOL;
struct line *resp = irc_line_new();
char *resps;
irc_line_append(resp, "PONG");
irc_line_append(resp, irc_line_elem(line, 1));
resp->colon = 1; /* it seems some ircds want it */
resps = irc_line_to_string(resp);
write_line_fast(CONN(server), resps);
irc_line_free(resp);
free(resps);
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 0, "PONG")) {
/* not all server reply with PONG <servername> <our string>
* so we blindly assume the PONG is ours. */
if (irc_line_count(line) == 2 || irc_line_count(line) == 3) {
if (server->laginit_ts != -1) {
irc_compute_lag(server);
irc_lag_init(server);
}
ret = OK_FORGET;
}
} else if (irc_line_elem_equals(line, 0, "CAP")) {
if (LINK(server)->sasl_mechanism) {
if (irc_line_elem_equals(line, 2, "ACK")
&& irc_line_elem_equals(line, 3, "sasl")) {
// Server is answering our CAP REQ :sasl and is
// SASL capable
char *sasl_mech = sasl_mechanism_to_text(
LINK(server)->sasl_mechanism);
mylog(LOG_INFO,
"[%s] Server is SASL capable, starting %s authentication.",
LINK(server)->name, sasl_mech);
WRITE_LINE1(CONN(server), NULL, "AUTHENTICATE",
sasl_mech);
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 2, "NAK")
&& irc_line_elem_equals(line, 3, "sasl")) {
// Server is answering our CAP REQ :sasl and
// isn't SASL capable
mylog(LOG_INFO,
"[%s] Server is not SASL capable.",
LINK(server)->name);
ret = ERR_PROTOCOL;
} else {
// Unhandled CAP message
mylog(LOG_ERROR,
"[%s] Unhandled CAP message: %s",
LINK(server)->name,
irc_line_to_string(line));
ret = OK_FORGET;
}
} else {
// Unhandled CAP message
mylog(LOG_ERROR, "[%s] Unhandled CAP message: %s",
LINK(server)->name, irc_line_to_string(line));
ret = OK_FORGET;
}
} else if (irc_line_elem_equals(line, 0, "AUTHENTICATE")) {
if (LINK(server)->sasl_mechanism) {
if (irc_line_count(line) == 2
&& irc_line_elem_equals(line, 1, "+")) {
// Server is waiting for us to authenticate,
// let's do it
mylog(LOG_INFO,
"[%s] Server accepted our authentication mechanism.",
LINK(server)->name);
ret = irc_server_sasl_authenticate(server);
} else {
// Anything else than "AUTHENTICATE +" is
// unknown to us
mylog(LOG_ERROR,
"[%s] Server sent gibberish: %s",
LINK(server)->name,
irc_line_to_string(line));
ret = ERR_PROTOCOL;
}
} else {
// Unhandled AUTHENTICATE message
mylog(LOG_ERROR,
"[%s] Unhandled AUTHENTICATE message: %s",
LINK(server)->name, irc_line_to_string(line));
ret = OK_FORGET;
}
} else if (irc_line_elem_equals(line, 0, "900")) {
if (irc_line_count(line) >= 5) {
mylog(LOG_INFO, "[%s] Logged in as %s(%s): %s",
LINK(server)->name, irc_line_elem(line, 3),
irc_line_elem(line, 2), irc_line_elem(line, 4));
} else {
mylog(LOG_INFO, "[%s] Logged in: %s",
LINK(server)->name, irc_line_to_string(line));
}
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 0, "901")) {
if (irc_line_count(line) >= 4) {
mylog(LOG_INFO, "[%s] Logged out: %s",
LINK(server)->name, irc_line_elem(line, 3));
} else {
mylog(LOG_INFO, "[%s] Logged out: %s",
LINK(server)->name, irc_line_to_string(line));
}
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 0, "902")) {
mylog(LOG_INFO, "[%s] Account unavailable: %s",
LINK(server)->name, irc_line_to_string(line));
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 0, "903")) {
mylog(LOG_INFO, "[%s] SASL authentication successful",
LINK(server)->name);
WRITE_LINE1(CONN(server), NULL, "CAP", "END");
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 0, "904")) {
mylog(LOG_ERROR, "[%s] SASL authentication failed",
LINK(server)->name);
ret = ERR_AUTH;
} else if (irc_line_elem_equals(line, 0, "905")) {
mylog(LOG_ERROR, "[%s] SASL message too long",
LINK(server)->name);
ret = ERR_AUTH;
} else if (irc_line_elem_equals(line, 0, "906")) {
mylog(LOG_ERROR, "[%s] SASL authentication aborted by client",
LINK(server)->name);
ret = ERR_AUTH;
} else if (irc_line_elem_equals(line, 0, "907")) {
mylog(LOG_ERROR,
"[%s] SASL authentication has already been completed",
LINK(server)->name);
ret = OK_FORGET;
} else if (irc_line_elem_equals(line, 0, "908")) {
mylog(LOG_ERROR,
"[%s] Server only accepts following authentication mechanisms: %s",
LINK(server)->name, irc_line_elem(line, 2));
ret = ERR_AUTH;
} else if (irc_line_elem_equals(line, 0, "433")) {
if (LINK(server)->s_state != IRCS_CONNECTED) {
size_t nicklen = strlen(server->nick);
char *newnick = bip_malloc(nicklen + 2);
strcpy(newnick, server->nick);
if (strlen(server->nick) < 9) {
strcat(newnick, "`");
} else {
if (newnick[7] != '`') {
if (newnick[8] != '`') {
newnick[8] = '`';
} else {
newnick[7] = '`';
}
} else {
newnick[8] =
(char)('a'
+ ('z' - 'a') * rand()
/ RAND_MAX);
}
newnick[9] = 0;
}
ls_set_nick(server, newnick);
WRITE_LINE1(CONN(server), NULL, "NICK", server->nick);
ret = OK_FORGET;
}
} else if (LINK(server)->s_state == IRCS_RECONNECTING) {
ret = OK_FORGET;
if (irc_line_elem_equals(line, 0, "376")) /* end of motd */
irc_server_connected(server);
else if (irc_line_elem_equals(line, 0, "422")) /* no motd */
irc_server_connected(server);
} else if (LINK(server)->s_state == IRCS_CONNECTING) {
ret = OK_FORGET;
if (irc_line_elem_equals(line, 0, "005")) {
int i;
for (i = irc_line_count(line) - 1; i > 0; i--) {
if (LINK(server)->ignore_server_capab
&& irc_line_elem_equals(line, i, "CAPAB"))
irc_line_drop(line, i);
else if (!strncmp(irc_line_elem(line, i),
"CHANMODES=", (size_t)10))
server_set_chanmodes(
server,
irc_line_elem(line, i) + 10);
else if (!strncmp(irc_line_elem(line, i),
"PREFIX=(", (size_t)8))
server_set_prefix(server,
irc_line_elem(line, i)
+ 7);
}
}
if (irc_line_elem_equals(line, 0, "NOTICE")) {
} else if (irc_line_elem_equals(line, 0, "376")) {
/* end of motd */
irc_server_connected(server);
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
} else if (irc_line_elem_equals(line, 0, "422")) { /* no motd */
irc_server_connected(server);
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
} else {
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
}
} else if (irc_line_elem_equals(line, 0, "001")) {
ret = irc_001(server, line);
if (LINK(server)->s_state == IRCS_CONNECTING) {
if (!list_is_empty(&LINK(server)->init_strings))
return ERR_PROTOCOL;
/* update the irc mask */
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
}
} else if (irc_line_elem_equals(line, 0, "JOIN")) {
ret = irc_join(server, line);
} else if (irc_line_elem_equals(line, 0, "332")) {
ret = irc_332(server, line);
} else if (irc_line_elem_equals(line, 0, "333")) {
ret = irc_333(server, line);
} else if (irc_line_elem_equals(line, 0, "352")) {
ret = irc_352(server, line);
} else if (irc_line_elem_equals(line, 0, "315")) {
ret = irc_315(server, line);
} else if (irc_line_elem_equals(line, 0, "353")) {
ret = irc_353(server, line);
} else if (irc_line_elem_equals(line, 0, "366")) {
ret = irc_366(server, line);
} else if (irc_line_elem_equals(line, 0, "367")) {
ret = irc_367(server, line);
} else if (irc_line_elem_equals(line, 0, "368")) {
ret = irc_368(server, line);
} else if (irc_line_elem_equals(line, 0, "PART")) {
ret = irc_part(server, line);
} else if (irc_line_elem_equals(line, 0, "MODE")) {
ret = irc_mode(server, line);
} else if (irc_line_elem_equals(line, 0, "TOPIC")) {
ret = irc_topic(server, line);
} else if (irc_line_elem_equals(line, 0, "KICK")) {
ret = irc_kick(server, line);
} else if (irc_line_elem_equals(line, 0, "PRIVMSG")) {
ret = irc_privmsg(server, line);
} else if (irc_line_elem_equals(line, 0, "NOTICE")) {
ret = irc_notice(server, line);
} else if (irc_line_elem_equals(line, 0, "QUIT")) {
ret = irc_quit(server, line);
} else if (irc_line_elem_equals(line, 0, "NICK")) {
ret = irc_nick(server, line);
}
if (ret == OK_COPY) {
unsigned int i;
for (i = 0; i < LINK(server)->l_clientc; i++) {
if (TYPE(LINK(server)->l_clientv[i])
== IRC_TYPE_CLIENT) {
char *s = irc_line_to_string(line);
write_line(CONN(LINK(server)->l_clientv[i]), s);
free(s);
}
}
}
if (ret == OK_COPY_WHO && LINK(server)->who_client) {
char *s;
s = irc_line_to_string(line);
write_line(CONN(LINK(server)->who_client), s);
free(s);
}
if (LINK(server)->who_client
&& LINK(server)->who_client->who_count == 0) {
mylog(LOG_DEBUG, "OK_COPY_WHO: who_count for %p is nul",
LINK(server)->who_client);
rotate_who_client(LINK(server));
}
return ret;
}
/* send join and related stuff to client */
static void irc_send_join(struct link_client *ic, struct channel *chan)
{
struct bipuser *user;
char *ircmask;
user = LINK(ic)->user;
assert(user);
/* user ircmask here for rbot */
ircmask = bip_malloc(strlen(LINK(ic)->l_server->nick)
+ strlen(BIP_FAKEMASK) + 1);
strcpy(ircmask, LINK(ic)->l_server->nick);
strcat(ircmask, BIP_FAKEMASK);
WRITE_LINE1(CONN(ic), ircmask, "JOIN", chan->name);
free(ircmask);
if (chan->topic)
WRITE_LINE3(CONN(ic), P_SERV, "332", LINK(ic)->l_server->nick,
chan->name, chan->topic);
if (chan->creator && chan->create_ts)
WRITE_LINE4(CONN(ic), P_SERV, "333", LINK(ic)->l_server->nick,
chan->name, chan->creator, chan->create_ts);
list_t *name_list = channel_name_list(LINK(ic)->l_server, chan);
char *s;
while ((s = list_remove_first(name_list))) {
char tmptype[2];
tmptype[0] = chan->type;
tmptype[1] = 0;
WRITE_LINE4(CONN(ic), P_SERV, "353", LINK(ic)->l_server->nick,
tmptype, chan->name, s);
free(s);
}
list_free(name_list);
WRITE_LINE3(CONN(ic), P_SERV, "366", LINK(ic)->l_server->nick,
chan->name, "End of /NAMES list.");
}
static void write_init_string(connection_t *c, struct line *line, char *nick)
{
char *l;
l = irc_line_to_string_to(line, nick);
write_line(c, l);
free(l);
}
static void bind_to_link(struct link *l, struct link_client *ic)
{
unsigned int i = l->l_clientc;
LINK(ic) = l;
l->l_clientc++;
l->l_clientv = bip_realloc(l->l_clientv,
l->l_clientc * sizeof(struct link_client *));
l->l_clientv[i] = ic;
}
void unbind_from_link(struct link_client *ic)
{
struct link *l = LINK(ic);
unsigned int i;
for (i = 0; i < l->l_clientc; i++)
if (l->l_clientv[i] == ic)
break;
assert(i != l->l_clientc);
if (l->who_client == ic) {
mylog(LOG_DEBUG, "unbind_from_link: %p: %d", l->who_client,
ic->who_count);
l->who_client = NULL;
}
for (i = i + 1; i < l->l_clientc; i++)
l->l_clientv[i - 1] = l->l_clientv[i];
if (l->l_clientc == 0)
fatal("unbind_from_link: negative client count");
l->l_clientc--;
l->l_clientv = bip_realloc(l->l_clientv,
l->l_clientc * sizeof(struct link_client *));
if (l->l_clientc == 0) { /* bip_realloc was equiv to free() */
l->l_clientv = NULL;
return;
}
}
int irc_cli_bip(bip_t *bip, struct link_client *ic, struct line *line)
{
return adm_bip(bip, ic, line, 0);
}
#define PASS_SEP ':'
static char *get_str_elem(char *str, int num)
{
char *ret;
char *c;
char *cur = str;
int index = 0;
while ((c = strchr(cur, PASS_SEP))) {
long len = c - cur;
if (index < num) {
index++;
cur = c + 1;
continue;
}
if (len < 1)
return NULL;
// len always > 0
ret = bip_malloc((size_t)len + 1);
memcpy(ret, cur, (size_t)len);
ret[len] = 0;
return ret;
}
if (index == num) {
long len;
c = str + strlen(str);
len = c - cur;
if (len < 1)
return NULL;
ret = bip_malloc((size_t)len + 1);
memcpy(ret, cur, (size_t)len);
ret[len] = 0;
return ret;
}
return NULL;
}
static void irc_cli_make_join(struct link_client *ic)
{
if (LINK(ic)->l_server) {
/* join channels, step one, those in conf, in order */
list_iterator_t li;
for (list_it_init(&LINK(ic)->chan_infos_order, &li);
list_it_item(&li); list_it_next(&li)) {
struct chan_info *ci =
(struct chan_info *)list_it_item(&li);
struct channel *chan;
if ((chan = hash_get(&LINK(ic)->l_server->channels,
ci->name)))
irc_send_join(ic, chan);
}
/* step two, those not in conf */
hash_iterator_t hi;
for (hash_it_init(&LINK(ic)->l_server->channels, &hi);
hash_it_item(&hi); hash_it_next(&hi)) {
struct channel *chan =
(struct channel *)hash_it_item(&hi);
if (!hash_get(&LINK(ic)->chan_infos, chan->name))
irc_send_join(ic, chan);
}
}
}
void irc_cli_backlog(struct link_client *ic, int hours)
{
struct bipuser *user;
user = LINK(ic)->user;
assert(user);
assert(LINK(ic)->l_server);
if (!user->backlog) {
mylog(LOG_DEBUG, "Backlog disabled for %s, not backlogging",
user->name);
return;
}
if (hours != 0) {
/* have some limit */
if (hours > 24 * 366)
hours = 24 * 366;
}
list_t *backlogl;
char *bl;
list_t *bllines;
backlogl = log_backlogs(LINK(ic)->log);
while ((bl = list_remove_first(backlogl))) {
bllines = backlog_lines(LINK(ic)->log, bl,
LINK(ic)->l_server->nick, hours);
if (bllines) {
if (!list_is_empty(bllines)) {
mylog(LOG_INFO, "[%s] backlogging: %s",
LINK(ic)->name, bl);
write_lines(CONN(ic), bllines);
}
list_free(bllines);
}
free(bl);
}
list_free(backlogl);
}
static int irc_cli_startup(bip_t *bip, struct link_client *ic,
struct line *line)
{
char *init_nick;
char *user, *pass, *connname;
(void)line;
assert(ic->init_pass);
user = get_str_elem(ic->init_pass, 0);
if (!user)
return ERR_AUTH;
pass = get_str_elem(ic->init_pass, 1);
if (!pass) {
free(user);
return ERR_AUTH;
}
connname = get_str_elem(ic->init_pass, 2);
if (!connname) {
free(pass);
free(user);
return ERR_AUTH;
}
list_iterator_t it;
for (list_it_init(&bip->link_list, &it); list_it_item(&it);
list_it_next(&it)) {
struct link *l = list_it_item(&it);
if (strcmp(user, l->user->name) == 0
&& strcmp(connname, l->name) == 0) {
if (chash_cmp(pass, l->user->password, l->user->seed)
== 0) {
bind_to_link(l, ic);
break;
}
}
}
if (!LINK(ic))
mylog(LOG_ERROR, "[%s] Invalid credentials (user: %s)",
connname, user);
free(user);
free(connname);
free(pass);
free(ic->init_pass);
ic->init_pass = NULL;
init_nick = ic->init_nick;
ic->init_nick = NULL;
if (!LINK(ic)) {
free(init_nick);
return ERR_AUTH;
}
#ifdef HAVE_LIBSSL
if (LINK(ic)->s_state != IRCS_CONNECTED) {
/* Check if we have an untrusted certificate from the server */
if (ssl_check_trust(ic)) {
free(init_nick);
return OK_FORGET;
}
}
#endif
if (LINK(ic)->s_state == IRCS_NONE) {
/* drop it if corresponding server hasn't connected at all. */
write_line_fast(CONN(ic),
":irc.bip.net NOTICE pouet "
":ERROR Proxy not yet connected, try again "
"later\r\n");
unbind_from_link(ic);
free(init_nick);
return OK_CLOSE;
}
list_remove(&bip->connecting_client_list, ic);
TYPE(ic) = IRC_TYPE_CLIENT;
for (list_it_init(&LINK(ic)->init_strings, &it); list_it_item(&it);
list_it_next(&it))
write_init_string(CONN(ic), list_it_item(&it), init_nick);
/* we change nick on server */
if (LINK(ic)->l_server) {
struct link_server *server = LINK(ic)->l_server;
WRITE_LINE1(CONN(ic), init_nick, "NICK", server->nick);
if (!LINK(ic)->ignore_first_nick)
WRITE_LINE1(CONN(server), NULL, "NICK", init_nick);
else if (LINK(ic)->away_nick
&& strcmp(LINK(ic)->away_nick, server->nick) == 0)
WRITE_LINE1(CONN(server), NULL, "NICK",
LINK(server)->connect_nick);
/* change away status */
if (server && LINK(ic)->no_client_away_msg)
WRITE_LINE0(CONN(server), NULL, "AWAY");
}
if (!LINK(ic)->l_server) {
free(init_nick);
return OK_FORGET;
}
irc_cli_make_join(ic);
irc_cli_backlog(ic, 0);
log_client_connected(LINK(ic)->log);
free(init_nick);
return OK_FORGET;
}
static int irc_cli_nick(bip_t *bip, struct link_client *ic, struct line *line)
{
if (irc_line_count(line) != 2)
return ERR_PROTOCOL;
if ((ic->state & IRCC_READY) == IRCC_READY)
return OK_COPY;
ic->state |= IRCC_NICK;
if (ic->init_nick)
free(ic->init_nick);
ic->init_nick = bip_strdup(irc_line_elem(line, 1));
if ((ic->state & IRCC_READY) == IRCC_READY)
return irc_cli_startup(bip, ic, line);
if ((ic->state & IRCC_PASS) != IRCC_PASS)
WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", ic->init_nick,
"You should type /QUOTE PASS your_username:"
"your_password:your_connection_name");
return OK_FORGET;
}
static int irc_cli_user(bip_t *bip, struct link_client *ic, struct line *line)
{
if (irc_line_count(line) != 5)
return ERR_PROTOCOL;
if ((ic->state & IRCC_READY) == IRCC_READY)
return ERR_PROTOCOL;
ic->state |= IRCC_USER;
if ((ic->state & IRCC_READY) == IRCC_READY)
return irc_cli_startup(bip, ic, line);
return OK_FORGET;
}
static int irc_cli_pass(bip_t *bip, struct link_client *ic, struct line *line)
{
if (irc_line_count(line) != 2)
return ERR_PROTOCOL;
if ((ic->state & IRCC_READY) == IRCC_READY)
return ERR_PROTOCOL;
ic->state |= IRCC_PASS;
if (ic->init_pass)
free(ic->init_pass);
ic->init_pass = bip_strdup(irc_line_elem(line, 1));
if ((ic->state & IRCC_READY) == IRCC_READY)
return irc_cli_startup(bip, ic, line);
return OK_FORGET;
}
static int irc_cli_quit(struct link_client *ic, struct line *line)
{
(void)ic;
(void)line;
return OK_CLOSE;
}
static int irc_cli_privmsg(bip_t *bip, struct link_client *ic,
struct line *line)
{
if (!irc_line_includes(line, 2))
return OK_FORGET;
if (irc_line_elem_equals(line, 1, "-bip"))
return adm_bip(bip, ic, line, 1);
else
log_cli_privmsg(LINK(ic)->log, LINK(ic)->l_server->nick,
irc_line_elem(line, 1), irc_line_elem(line, 2));
if (LINK(ic)->user->blreset_on_talk) {
if (LINK(ic)->user->blreset_connection)
log_reset_all(LINK(ic)->log);
else
log_reset_store(LINK(ic)->log, irc_line_elem(line, 1));
}
return OK_COPY_CLI;
}
static int irc_cli_notice(struct link_client *ic, struct line *line)
{
if (!irc_line_includes(line, 2))
return OK_FORGET;
log_cli_notice(LINK(ic)->log, LINK(ic)->l_server->nick,
irc_line_elem(line, 1), irc_line_elem(line, 2));
if (LINK(ic)->user->blreset_on_talk) {
if (LINK(ic)->user->blreset_connection)
log_reset_all(LINK(ic)->log);
else
log_reset_store(LINK(ic)->log, irc_line_elem(line, 1));
}
return OK_COPY_CLI;
}
static int irc_cli_who(struct link_client *ic, struct line *line)
{
struct link *l = LINK(ic);
++ic->who_count;
if (ic->who_count == 1)
ic->whoc_tstamp = time(NULL);
mylog(LOG_DEBUG, "cli_who: Incrementing who count for %p: %d", ic,
ic->who_count);
if (l->who_client && l->who_client != ic) {
list_add_first(&ic->who_queue, irc_line_to_string(line));
return OK_FORGET;
}
if (!l->who_client)
l->who_client = ic;
return OK_COPY;
}
static int irc_cli_mode(struct link_client *ic, struct line *line)
{
struct link *l = LINK(ic);
if (irc_line_count(line) != 3)
return OK_COPY;
/* This is a wild guess and that sucks. */
if (!irc_line_elem_equals(line, 0, "MODE")
|| strchr(irc_line_elem(line, 2), 'b') == NULL)
return OK_COPY;
++ic->who_count;
if (ic->who_count == 1)
ic->whoc_tstamp = time(NULL);
mylog(LOG_DEBUG, "cli_mode: Incrementing who count for %p: %d",
l->who_client, ic->who_count);
if (l->who_client && l->who_client != ic) {
list_add_first(&ic->who_queue, irc_line_to_string(line));
return OK_FORGET;
}
if (!l->who_client)
l->who_client = ic;
return OK_COPY;
}
static void irc_notify_disconnection(struct link_server *is)
{
unsigned int i;
LINK(is)->cli_nick = bip_strdup(is->nick);
for (i = 0; i < LINK(is)->l_clientc; i++) {
struct link_client *ic = LINK(is)->l_clientv[i];
hash_iterator_t hi;
for (hash_it_init(&is->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
struct channel *c = (struct channel *)hash_it_item(&hi);
WRITE_LINE3(CONN(ic), P_IRCMASK, "KICK", c->name,
is->nick,
"Server disconnected, reconnecting");
}
bip_notify(ic, "Server disconnected, reconnecting");
}
}
void irc_add_channel_info(struct link_server *ircs, const char *chan,
const char *key)
{
struct chan_info *ci;
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (!ischannel(*chan))
return;
#pragma GCC diagnostic pop
ci = hash_get(&LINK(ircs)->chan_infos, chan);
if (!ci) {
ci = chan_info_new();
ci->name = bip_strdup(chan);
ci->key = key ? bip_strdup(key) : NULL;
ci->backlog = 1;
hash_insert(&LINK(ircs)->chan_infos, chan, ci);
list_add_last(&LINK(ircs)->chan_infos_order, ci);
} else {
if (ci->key) {
free(ci->key);
ci->key = NULL;
}
ci->key = key ? bip_strdup(key) : NULL;
}
}
static int irc_cli_join(struct link_client *irc, struct line *line)
{
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
return ERR_PROTOCOL;
const char *s, *e, *ks, *ke = NULL;
s = irc_line_elem(line, 1);
if (irc_line_count(line) == 3)
ks = irc_line_elem(line, 2);
else
ks = NULL;
while ((e = strchr(s, ','))) {
size_t len = (size_t)(e - s); // cannot be < 0 or NULL per while
char *p = bip_malloc(len + 1);
size_t klen;
char *kp = NULL;
memcpy(p, s, len);
p[len] = 0;
if (ks) {
if (strlen(ks)) {
ke = strchr(ks, ',');
if (!ke)
ke = ks + strlen(ks);
klen = (size_t)(ke - ks);
kp = bip_malloc(klen + 1);
memcpy(kp, ks, klen);
kp[klen] = 0;
if (*ke == 0)
ks = NULL;
} else {
kp = NULL;
ks = NULL;
}
}
irc_add_channel_info(LINK(irc)->l_server, p, kp);
free(p);
if (kp) {
free(kp);
if (ks)
ks = ke + 1;
}
s = e + 1;
}
irc_add_channel_info(LINK(irc)->l_server, s, ks);
return OK_COPY;
}
static int irc_cli_part(struct link_client *irc, struct line *line)
{
struct chan_info *ci;
const char *cname;
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
return ERR_PROTOCOL;
cname = irc_line_elem(line, 1);
if ((ci = hash_remove_if_exists(&LINK(irc)->chan_infos, cname))
!= NULL) {
list_remove(&LINK(irc)->chan_infos_order, ci);
free(ci->name);
if (ci->key)
free(ci->key);
free(ci);
}
return OK_COPY;
}
#ifdef HAVE_LIBSSL
static int irc_dispatch_trust_client(struct link_client *ic, struct line *line)
{
int r = OK_COPY;
if (!irc_line_includes(line, 1))
return ERR_PROTOCOL;
if (strcasecmp(irc_line_elem(line, 0), "BIP") == 0
&& strcasecmp(irc_line_elem(line, 1), "TRUST") == 0)
r = adm_trust(ic, line);
return r;
}
#endif
static int irc_dispatch_client(bip_t *bip, struct link_client *ic,
struct line *line)
{
int r = OK_COPY;
if (irc_line_count(line) == 0)
return ERR_PROTOCOL;
if (irc_line_elem_equals(line, 0, "PING")) {
if (!irc_line_includes(line, 1))
return ERR_PROTOCOL;
WRITE_LINE1(CONN(ic), link_name((struct link_any *)ic), "PONG",
irc_line_elem(line, 1));
r = OK_FORGET;
} else if (LINK(ic)->s_state != IRCS_CONNECTED) {
write_line_fast(CONN(ic),
":irc.bip.net NOTICE pouet "
":ERROR Proxy not connected, please wait "
"before sending commands\r\n");
r = OK_FORGET;
} else if (strcasecmp(irc_line_elem(line, 0), "BIP") == 0) {
r = irc_cli_bip(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "JOIN")) {
r = irc_cli_join(ic, line);
} else if (irc_line_elem_equals(line, 0, "PART")) {
r = irc_cli_part(ic, line);
} else if (irc_line_elem_equals(line, 0, "NICK")) {
r = irc_cli_nick(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "QUIT")) {
r = irc_cli_quit(ic, line);
} else if (irc_line_elem_equals(line, 0, "PRIVMSG")) {
r = irc_cli_privmsg(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "NOTICE")) {
r = irc_cli_notice(ic, line);
} else if (irc_line_elem_equals(line, 0, "WHO")) {
r = irc_cli_who(ic, line);
} else if (irc_line_elem_equals(line, 0, "MODE")) {
r = irc_cli_mode(ic, line);
}
if (r == OK_COPY || r == OK_COPY_CLI) {
char *str = irc_line_to_string(line);
if (LINK(ic)->s_state == IRCS_CONNECTED
&& LINK(ic)->l_server->nick)
write_line(CONN(LINK(ic)->l_server), str);
else if (LINK(ic)->l_server->nick)
WRITE_LINE2(CONN(ic), P_IRCMASK,
(LINK(ic)->user->bip_use_notice
? "NOTICE"
: "PRIVMSG"),
LINK(ic)->l_server->nick,
":Not connected please try again "
"later...\r\n");
free(str);
if (r == OK_COPY_CLI) {
unsigned int i;
struct link_server *s = LINK(ic)->l_server;
for (i = 0; i < LINK(s)->l_clientc; i++)
irc_copy_cli(ic, LINK(s)->l_clientv[i], line);
}
}
return r;
}
static void irc_copy_cli(struct link_client *src, struct link_client *dest,
struct line *line)
{
char *str;
if (src == dest)
return;
if (!irc_line_includes(line, 1)
|| !irc_line_elem_equals(line, 0, "PRIVMSG")) {
str = irc_line_to_string(line);
write_line(CONN(dest), str);
free(str);
return;
}
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (ischannel(*irc_line_elem(line, 1)) || LINK(src) != LINK(dest)) {
assert(!line->origin);
line->origin = LINK(src)->l_server->nick;
str = irc_line_to_string(line);
line->origin = NULL;
write_line(CONN(dest), str);
free(str);
return;
}
#pragma GCC diagnostic pop
/* LINK(src) == LINK(dest) */
size_t len = strlen(irc_line_elem(line, 2)) + 6;
// snprintf fix ^
// __builtin___snprintf_chk output may be truncated before the last
// format character
char *tmp;
if (len == 0)
return;
tmp = bip_malloc(len);
snprintf(tmp, len, " -> %s", irc_line_elem(line, 2));
tmp[len - 1] = 0;
struct line *retline = irc_line_new();
retline->origin = bip_strdup(irc_line_elem(line, 1));
irc_line_append(retline, irc_line_elem(line, 0));
irc_line_append(retline, LINK(src)->l_server->nick);
irc_line_append(retline, tmp);
free(tmp);
str = irc_line_to_string(retline);
irc_line_free(retline);
#if 0
/* tricky: */
irc_line_elem(line, 1) = LINK(src)->l_server->nick;
oldelem = irc_line_elem(line, 2);
irc_line_elem(line, 2) = tmp;
str = irc_line_to_string(line);
/* end of trick: */
irc_line_elem(line, 1) = line->origin;
irc_line_elem(line, 2) = oldelem;
line->origin = NULL;
#endif
write_line(CONN(dest), str);
free(str);
return;
}
static int irc_dispatch_logging_client(bip_t *bip, struct link_client *ic,
struct line *line)
{
if (irc_line_count(line) == 0)
return ERR_PROTOCOL;
if (irc_line_elem_equals(line, 0, "NICK")) {
return irc_cli_nick(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "USER")) {
return irc_cli_user(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "PASS")) {
return irc_cli_pass(bip, ic, line);
}
return OK_FORGET;
}
int irc_dispatch(bip_t *bip, struct link_any *l, struct line *line)
{
switch (TYPE(l)) {
case IRC_TYPE_SERVER:
return irc_dispatch_server(bip, (struct link_server *)l, line);
break;
case IRC_TYPE_CLIENT:
return irc_dispatch_client(bip, (struct link_client *)l, line);
break;
case IRC_TYPE_LOGGING_CLIENT:
return irc_dispatch_logging_client(bip, (struct link_client *)l,
line);
break;
#ifdef HAVE_LIBSSL
case IRC_TYPE_TRUST_CLIENT:
return irc_dispatch_trust_client((struct link_client *)l, line);
break;
#endif
default:
fatal("irc_dispatch: unknown IRC_TYPE_SERVER");
}
return ERR_PROTOCOL; /* never reached */
}
static int origin_is_me(struct line *l, struct link_server *server)
{
char *nick;
if (!l->origin)
return 0;
nick = nick_from_ircmask(l->origin);
if (!nick)
return 0;
if (strcasecmp(nick, server->nick) == 0) {
free(nick);
return 1;
}
free(nick);
return 0;
}
static int irc_join(struct link_server *server, struct line *line)
{
char *s_nick;
const char *s_chan;
struct channel *channel;
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
return ERR_PROTOCOL;
s_chan = irc_line_elem(line, 1);
log_join(LINK(server)->log, line->origin, s_chan);
channel = hash_get(&server->channels, s_chan);
if (origin_is_me(line, server)) {
if (!channel) {
channel = channel_new(s_chan);
hash_insert(&server->channels, s_chan, channel);
}
return OK_COPY;
}
/* if we're not on channel and !origin_is_me, we should not get any
* JOIN */
if (!channel)
return ERR_PROTOCOL;
if (!line->origin)
return ERR_PROTOCOL;
s_nick = nick_from_ircmask(line->origin);
// should not happen
if (!s_nick)
return ERR_PROTOCOL;
hash_insert(&channel->ovmasks, s_nick, 0);
free(s_nick);
return OK_COPY;
}
static int irc_332(struct link_server *server, struct line *line)
{
struct channel *channel;
if (irc_line_count(line) != 4)
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
/* we can get topic reply for chans we're not on */
if (!channel)
return OK_COPY;
if (channel->topic)
free(channel->topic);
channel->topic = bip_strdup(irc_line_elem(line, 3));
log_init_topic(LINK(server)->log, channel->name, channel->topic);
return OK_COPY;
}
static int irc_333(struct link_server *server, struct line *line)
{
struct channel *channel;
if (!irc_line_includes(line, 2))
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
/* we can get topic info reply for chans we're not on */
if (!channel)
return OK_COPY;
if (channel->creator)
free(channel->creator);
if (channel->create_ts)
free(channel->create_ts);
if (irc_line_count(line) == 5) {
channel->creator = bip_strdup(irc_line_elem(line, 3));
channel->create_ts = bip_strdup(irc_line_elem(line, 4));
} else {
channel->creator = bip_strdup("");
channel->create_ts = bip_strdup("0");
}
log_init_topic_time(LINK(server)->log, channel->name, channel->creator,
channel->create_ts);
return OK_COPY;
}
static int irc_353(struct link_server *server, struct line *line)
{
struct channel *channel;
const char *names, *eon;
size_t len;
char *nick;
if (irc_line_count(line) != 5)
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 3));
/* we can get names reply for chans we're not on */
if (!channel)
return OK_COPY;
if (!channel->running_names) {
channel->running_names = 1;
hash_clean(&channel->ovmasks);
}
/* TODO check that type is one of "=" / "*" / "@" */
channel->type = irc_line_elem(line, 2)[0];
names = irc_line_elem(line, 4);
int index;
while (*names) {
long int ovmask = 0;
/* some ircds (e.g. unreal) may display several flags for the
same nick */
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
while ((index = bip_get_index(server->prefixes, *names))) {
ovmask |= 1 << index;
names++;
}
#pragma GCC diagnostic pop
eon = names;
while (*eon && *eon != ' ')
eon++;
len = (size_t)(eon - names); // cannot be < 0
nick = bip_malloc(len + 1);
memcpy(nick, names, len);
nick[len] = 0;
/* we just ignore names for nicks that are crazy long */
if (len + 2 < NAMESIZE)
hash_insert(&channel->ovmasks, nick, (void *)ovmask);
free(nick);
while (*eon && *eon == ' ')
eon++;
names = eon;
}
return OK_COPY;
}
static int irc_366(struct link_server *server, struct line *line)
{
struct channel *channel;
if (irc_line_count(line) != 4)
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
if (channel && channel->running_names)
channel->running_names = 0;
return OK_COPY;
}
static int irc_367(struct link_server *server, struct line *l)
{
(void)server;
(void)l;
return OK_COPY_WHO;
}
/* same as irc_315 */
static int irc_368(struct link_server *server, struct line *l)
{
(void)l;
struct link *link = LINK(server);
if (link->who_client) {
if (link->who_client->who_count == 0) {
mylog(LOG_DEBUG, "Spurious irc_368");
return OK_COPY_WHO;
}
link->who_client->whoc_tstamp = time(NULL);
if (link->who_client->who_count > 0) {
--link->who_client->who_count;
mylog(LOG_DEBUG,
"RPL_ENDOFBANLIST: "
"Decrementing who count for %p: %d",
link->who_client, link->who_client->who_count);
}
}
return OK_COPY_WHO;
}
static void channel_free(struct channel *c)
{
if (c->name)
free(c->name);
if (c->mode)
free(c->mode);
if (c->key)
free(c->key);
if (c->topic)
free(c->topic);
if (c->creator)
free(c->creator);
if (c->create_ts)
free(c->create_ts);
hash_clean(&c->ovmasks);
free(c);
}
static int irc_part(struct link_server *server, struct line *line)
{
char *s_nick;
const char *s_chan;
struct channel *channel;
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
return ERR_PROTOCOL;
s_chan = irc_line_elem(line, 1);
channel = hash_get(&server->channels, s_chan);
/* we can't get part message for chans we're not on */
if (!channel)
return ERR_PROTOCOL;
if (origin_is_me(line, server)) {
log_part(LINK(server)->log, line->origin, s_chan,
irc_line_count(line) == 3 ? irc_line_elem(line, 2)
: NULL);
log_reset_store(LINK(server)->log, s_chan);
log_drop(LINK(server)->log, s_chan);
hash_remove(&server->channels, s_chan);
channel_free(channel);
return OK_COPY;
}
if (!line->origin)
return ERR_PROTOCOL;
s_nick = nick_from_ircmask(line->origin);
// should not happen
if (!s_nick)
return ERR_PROTOCOL;
if (!hash_includes(&channel->ovmasks, s_nick)) {
free(s_nick);
return ERR_PROTOCOL;
}
hash_remove(&channel->ovmasks, s_nick);
free(s_nick);
log_part(LINK(server)->log, line->origin, s_chan,
irc_line_count(line) == 3 ? irc_line_elem(line, 2) : NULL);
return OK_COPY;
}
static void mode_add_letter_uniq(struct link_server *s, char c)
{
size_t i;
for (i = 0; i < s->user_mode_len; i++) {
if (s->user_mode[i] == c)
return;
}
s->user_mode = bip_realloc(s->user_mode, s->user_mode_len + 1);
s->user_mode[s->user_mode_len++] = c;
}
static void mode_remove_letter(struct link_server *s, char c)
{
size_t i;
for (i = 0; i < s->user_mode_len; i++) {
if (s->user_mode[i] == c) {
for (; i < s->user_mode_len - 1; i++)
s->user_mode[i] = s->user_mode[i + 1];
s->user_mode_len--;
s->user_mode =
bip_realloc(s->user_mode, s->user_mode_len);
return;
}
}
}
static void irc_user_mode(struct link_server *server, struct line *line)
{
const char *mode;
int add = 1;
for (mode = irc_line_elem(line, 2); *mode; mode++) {
if (*mode == '-')
add = 0;
else if (*mode == '+')
add = 1;
else {
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (add) {
mode_add_letter_uniq(server, *mode);
} else {
mode_remove_letter(server, *mode);
}
#pragma GCC diagnostic pop
}
}
}
static int irc_mode(struct link_server *server, struct line *line)
{
struct channel *channel;
const char *mode;
int add = 1;
int cur_arg = 0;
array_t *mode_args = NULL;
int ret;
if (!irc_line_includes(line, 2))
return ERR_PROTOCOL;
/* nick mode change */
if (irc_line_elem_equals(line, 1, server->nick)) {
if (irc_line_includes(line, 3))
mode_args = array_extract(&line->words, 3, -1);
log_mode(LINK(server)->log, line->origin,
irc_line_elem(line, 1), irc_line_elem(line, 2),
mode_args);
if (mode_args)
array_free(mode_args);
irc_user_mode(server, line);
return OK_COPY;
}
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (!ischannel(irc_line_elem(line, 1)[0]))
return ERR_PROTOCOL;
#pragma GCC diagnostic pop
/* channel mode change */
channel = hash_get(&server->channels, irc_line_elem(line, 1));
/* we can't get mode message for chans we're not on */
if (!channel)
return ERR_PROTOCOL;
mode_args = NULL;
if (irc_line_includes(line, 3))
mode_args = array_extract(&line->words, 3, -1);
log_mode(LINK(server)->log, line->origin, irc_line_elem(line, 1),
irc_line_elem(line, 2), mode_args);
if (mode_args)
array_free(mode_args);
/*
* MODE -a+b.. #channel args
* ^ ^
* mode cur_arg
*/
for (mode = irc_line_elem(line, 2); *mode; mode++) {
if (*mode == '-')
add = 0;
else if (*mode == '+')
add = 1;
else {
int i = 0;
char *str = 0;
// Check if mode is known: first user modes then
// server modes
if (!(str = strchr(server->usermodes, *mode))) {
array_each(&server->chanmodes, i, str)
{
if ((str = strchr(str, *mode)))
break;
}
}
if (str) {
// Usermodes, types A & B always take a
// parameter Type C take a parameter only when
// set
if (i <= 1 || (i == 2 && add)) {
if (!irc_line_includes(line,
cur_arg + 3)) {
return ERR_PROTOCOL;
} else {
ret = irc_mode_channel(
server, channel, line,
mode, add, cur_arg);
cur_arg++;
}
} else {
ret = irc_mode_channel(server, channel,
line, mode, add,
cur_arg);
}
}
}
if (ret == ERR_PROTOCOL)
return ret;
}
return OK_COPY;
}
static int irc_mode_channel(struct link_server *s, struct channel *channel,
struct line *line, const char *mode, int add,
int cur_arg)
{
const char *nick;
long int ovmask;
int index;
// TODO resolve this issue from array_get
// passing argument X of .... with different width due to prototype
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtraditional-conversion"
if (*mode == 'k') {
if (add) {
channel->key =
bip_strdup(irc_line_elem(line, cur_arg + 3));
} else {
if (channel->key) {
free(channel->key);
channel->key = NULL;
}
}
} else if ((index = bip_get_index(s->usermodes, *mode))) {
nick = irc_line_elem(line, cur_arg + 3);
if (!hash_includes(&channel->ovmasks, nick))
return ERR_PROTOCOL;
ovmask = (long int)hash_remove(&channel->ovmasks, nick);
if (add)
ovmask |= 1 << index;
else
ovmask &= ~(1 << index);
hash_insert(&channel->ovmasks, nick, (void *)ovmask);
}
#pragma GCC diagnostic pop
return OK_COPY;
}
static char *irc_timestamp(void)
{
char *ts = bip_malloc((size_t)23);
snprintf(ts, (size_t)22, "%ld", (long int)time(NULL));
return ts;
}
static int irc_topic(struct link_server *server, struct line *line)
{
struct channel *channel;
const char *topic;
if (irc_line_count(line) != 3)
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 1));
/* we can't get topic message for chans we're not on */
if (!channel)
return ERR_PROTOCOL;
if (channel->topic)
free(channel->topic);
topic = irc_line_elem(line, 2);
if (*topic == ':')
topic++;
channel->topic = bip_strdup(topic);
/*
* :arion.oftc.net 333 bip`luser #bipqSDFQE3
* nohar!~nohar@borne28.noc.nerim.net 1107338095
*/
if (channel->creator)
free(channel->creator);
channel->creator = bip_strmaydup(line->origin);
if (channel->create_ts)
free(channel->create_ts);
channel->create_ts = irc_timestamp();
log_topic(LINK(server)->log, line->origin, irc_line_elem(line, 1),
topic);
return OK_COPY;
}
static int irc_kick(struct link_server *server, struct line *line)
{
struct channel *channel;
if (irc_line_count(line) != 3 && irc_line_count(line) != 4)
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 1));
/* we can't get kick message for chans we're not on */
if (!channel)
return ERR_PROTOCOL;
if (!hash_includes(&channel->ovmasks, irc_line_elem(line, 2)))
return ERR_PROTOCOL;
if (strcasecmp(irc_line_elem(line, 2), server->nick) == 0) {
/* we get kicked !! */
log_kick(LINK(server)->log, line->origin, channel->name,
irc_line_elem(line, 2),
irc_line_count(line) == 4 ? irc_line_elem(line, 3)
: NULL);
log_reset_store(LINK(server)->log, channel->name);
log_drop(LINK(server)->log, channel->name);
if (LINK(server)->autojoin_on_kick) {
if (!channel->key)
WRITE_LINE1(CONN(server), NULL, "JOIN",
channel->name);
else
WRITE_LINE2(CONN(server), NULL, "JOIN",
channel->name, channel->key);
}
hash_remove(&server->channels, channel->name);
channel_free(channel);
return OK_COPY;
}
hash_remove(&channel->ovmasks, irc_line_elem(line, 2));
log_kick(LINK(server)->log, line->origin, irc_line_elem(line, 1),
irc_line_elem(line, 2),
irc_line_count(line) == 4 ? irc_line_elem(line, 3) : NULL);
return OK_COPY;
}
static void irc_privmsg_check_ctcp(struct link_server *server,
struct line *line)
{
if (irc_line_count(line) != 3)
return;
if (!line->origin)
return;
char *nick;
nick = nick_from_ircmask(line->origin);
// should not happen
if (!nick)
return;
if (irc_line_elem_equals(line, 2, "\001VERSION\001")) {
WRITE_LINE2(CONN(server), NULL, "NOTICE", nick,
"\001VERSION bip-" PACKAGE_VERSION "\001");
}
free(nick);
}
static int irc_privmsg(struct link_server *server, struct line *line)
{
if (!irc_line_includes(line, 2))
return ERR_PROTOCOL;
if (LINK(server)->s_state == IRCS_CONNECTED)
log_privmsg(LINK(server)->log, line->origin,
irc_line_elem(line, 1), irc_line_elem(line, 2));
irc_privmsg_check_ctcp(server, line);
return OK_COPY;
}
static int irc_notice(struct link_server *server, struct line *line)
{
if (!irc_line_includes(line, 2))
return ERR_PROTOCOL;
if (LINK(server)->s_state == IRCS_CONNECTED)
log_notice(LINK(server)->log, line->origin,
irc_line_elem(line, 1), irc_line_elem(line, 2));
return OK_COPY;
}
static int irc_quit(struct link_server *server, struct line *line)
{
return irc_generic_quit(server, line);
}
static int irc_nick(struct link_server *server, struct line *line)
{
struct channel *channel;
hash_iterator_t hi;
char *org_nick;
const char *dst_nick;
if (irc_line_count(line) != 2)
return ERR_PROTOCOL;
if (!line->origin)
return ERR_PROTOCOL;
org_nick = nick_from_ircmask(line->origin);
// should not happen
if (!org_nick)
return ERR_PROTOCOL;
dst_nick = irc_line_elem(line, 1);
for (hash_it_init(&server->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
channel = hash_it_item(&hi);
if (!hash_includes(&channel->ovmasks, org_nick))
continue;
hash_rename_key(&channel->ovmasks, org_nick, dst_nick);
log_nick(LINK(server)->log, org_nick, channel->name, dst_nick);
}
if (origin_is_me(line, server)) {
free(server->nick);
server->nick = bip_strdup(dst_nick);
if (LINK(server)->follow_nick
&& (LINK(server)->away_nick == NULL
|| strcmp(server->nick, LINK(server)->away_nick))
!= 0) {
free(LINK(server)->connect_nick);
LINK(server)->connect_nick = bip_strdup(server->nick);
}
}
free(org_nick);
return OK_COPY;
}
static int irc_generic_quit(struct link_server *server, struct line *line)
{
struct channel *channel;
hash_iterator_t hi;
char *s_nick;
if (irc_line_count(line) != 2 && irc_line_count(line) != 1)
return ERR_PROTOCOL;
if (!line->origin)
return ERR_PROTOCOL;
s_nick = nick_from_ircmask(line->origin);
// should not happen
if (!s_nick)
return ERR_PROTOCOL;
for (hash_it_init(&server->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
channel = hash_it_item(&hi);
if (!hash_includes(&channel->ovmasks, s_nick))
continue;
hash_remove(&channel->ovmasks, s_nick);
log_quit(LINK(server)->log, line->origin, channel->name,
irc_line_includes(line, 1) ? irc_line_elem(line, 1)
: NULL);
}
free(s_nick);
return OK_COPY;
}
static void ls_set_nick(struct link_server *ircs, char *nick)
{
if (ircs->nick)
free(ircs->nick);
ircs->nick = nick;
#if 0
if (ircs->ircmask) {
char *eom = strchr(ircs->ircmask, '!');
if (!eom) {
free(ircs->ircmask);
goto fake;
}
eom = bip_strdup(eom);
free(ircs->ircmask);
ircs->ircmask = bip_malloc(strlen(nick) + strlen(eom) + 1);
strcpy(ircs->ircmask, nick);
strcat(ircs->ircmask, eom);
free(eom);
return;
}
fake:
ircs->ircmask = bip_malloc(strlen(nick) + strlen(BIP_FAKEMASK) + 1);
strcpy(ircs->ircmask, nick);
strcat(ircs->ircmask, BIP_FAKEMASK);
#endif
}
static char *sasl_mechanism_to_text(int sasl_mechanism)
{
switch (sasl_mechanism) {
case SASL_AUTH_EXTERNAL:
return "EXTERNAL";
case SASL_AUTH_PLAIN:
return "PLAIN";
default:
return "UNKOWN_MECHANISM";
}
}
// Per RFC send packets of max 400 chars at a time
#define SASL_AUTH_CHUNK_SZ 400
static int irc_server_sasl_authenticate(struct link_server *ircs)
{
char *sasl_username = LINK(ircs)->sasl_username;
char *sasl_password = LINK(ircs)->sasl_password;
if (LINK(ircs)->sasl_mechanism == SASL_AUTH_EXTERNAL) {
WRITE_LINE1(CONN(ircs), NULL, "AUTHENTICATE", "+");
return OK_FORGET;
}
// Should not happen, but we never know right ?
if (!sasl_username || !sasl_password) {
mylog(LOG_ERROR, "[%s] Missing SASL username or password.",
LINK(ircs)->name);
return ERR_AUTH;
}
/*
* Other than EXTERNAL we only support PLAIN.
*/
size_t chunk_chars = SASL_AUTH_CHUNK_SZ;
char chunk[SASL_AUTH_CHUNK_SZ + 1];
size_t u_len = strlen(sasl_username);
size_t p_len = strlen(sasl_password);
size_t raw_len = u_len * 2 + p_len + 2;
size_t enc_len;
char *raw_str = bip_malloc(raw_len + 1);
char *enc_str;
memcpy(raw_str, sasl_username, u_len);
raw_str[u_len] = '\0';
memcpy(raw_str + u_len + 1, sasl_username, u_len);
raw_str[u_len * 2 + 1] = '\0';
memcpy(raw_str + u_len * 2 + 2, sasl_password, p_len);
enc_str = base64_encode(raw_str, raw_len, &enc_len);
mylog(LOG_DEBUG, "[%s] Base64 encoded SASL auth token (len %d): %s",
LINK(ircs)->name, enc_len, enc_str);
for (size_t i = 0; i < enc_len; i += chunk_chars) {
size_t remaining = enc_len - i;
if (remaining < chunk_chars) {
memcpy(chunk, &enc_str[i], remaining);
chunk[remaining] = '\0';
} else {
memcpy(chunk, &enc_str[i], chunk_chars);
chunk[chunk_chars] = '\0';
}
mylog(LOG_DEBUG, "[%s] SASL AUTHENTICATE chunk %d, len %d: %s",
LINK(ircs)->name, i / chunk_chars, strlen(chunk), chunk);
WRITE_LINE1(CONN(ircs), NULL, "AUTHENTICATE", chunk);
// Send a closing AUTHENTICATE line if last chunk size was
// exactly 400
if (remaining == chunk_chars) {
mylog(LOG_DEBUG,
"[%s] Last SASL chunk was exactly 400, sending +",
LINK(ircs)->name);
WRITE_LINE1(CONN(ircs), NULL, "AUTHENTICATE", "+");
break;
}
}
free(enc_str);
return OK_FORGET;
}
static void irc_server_startup(struct link_server *ircs)
{
char *nick;
char *username, *realname;
/* lower the token number as freenode hates fast login */
CONN(ircs)->token = 1;
if (LINK(ircs)->s_password)
WRITE_LINE1(CONN(ircs), NULL, "PASS", LINK(ircs)->s_password);
username = LINK(ircs)->username;
if (!username)
username = LINK(ircs)->user->default_username;
realname = LINK(ircs)->realname;
if (!realname)
realname = LINK(ircs)->user->default_realname;
WRITE_LINE4(CONN(ircs), NULL, "USER", username, "0", "*", realname);
nick = ircs->nick;
if (LINK(ircs)->away_nick && LINK(ircs)->l_clientc == 0) {
if (nick)
free(nick);
nick = bip_strdup(LINK(ircs)->away_nick);
}
if ((!LINK(ircs)->follow_nick && !LINK(ircs)->away_nick)
|| nick == NULL) {
if (nick)
free(nick);
if (!LINK(ircs)->connect_nick)
LINK(ircs)->connect_nick =
bip_strdup(LINK(ircs)->user->default_nick);
nick = bip_strdup(LINK(ircs)->connect_nick);
}
ls_set_nick(ircs, nick);
WRITE_LINE1(CONN(ircs), NULL, "NICK", ircs->nick);
}
static void server_next(struct link *l)
{
l->cur_server++;
if (l->cur_server >= l->network->serverc)
l->cur_server = 0;
}
static struct link_client *irc_accept_new(connection_t *conn)
{
struct link_client *ircc;
connection_t *newconn;
newconn = accept_new(conn);
if (!newconn)
return NULL;
ircc = bip_calloc(sizeof(struct link_client), (size_t)1);
CONN(ircc) = newconn;
TYPE(ircc) = IRC_TYPE_LOGGING_CLIENT;
CONN(ircc)->user_data = ircc;
return ircc;
}
void server_cleanup(struct link_server *server)
{
if (server->nick) {
free(server->nick);
server->nick = NULL;
}
if (LINK(server)->s_state == IRCS_CONNECTED) {
LINK(server)->s_state = IRCS_WAS_CONNECTED;
} else {
struct line *s;
LINK(server)->s_state = IRCS_NONE;
while ((s = list_remove_first(&LINK(server)->init_strings)))
irc_line_free(s);
}
hash_iterator_t hi;
for (hash_it_init(&server->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi))
channel_free(hash_it_item(&hi));
hash_clean(&server->channels);
if (CONN(server)) {
connection_free(CONN(server));
CONN(server) = NULL;
}
irc_lag_init(server);
}
void irc_client_close(struct link_client *ic)
{
if (TYPE(ic) == IRC_TYPE_CLIENT) {
struct link_server *is = LINK(ic)->l_server;
log_client_disconnected(LINK(ic)->log);
unbind_from_link(ic);
if (LINK(ic)->l_clientc == 0) {
if (is && LINK(ic)->away_nick)
WRITE_LINE1(CONN(is), NULL, "NICK",
LINK(ic)->away_nick);
if (is && LINK(ic)->no_client_away_msg)
WRITE_LINE1(CONN(is), NULL, "AWAY",
LINK(ic)->no_client_away_msg);
log_client_none_connected(LINK(ic)->log);
}
irc_client_free(ic);
} else if (TYPE(ic) == IRC_TYPE_TRUST_CLIENT) {
unbind_from_link(ic);
irc_client_free(ic);
} else if (TYPE(ic) == IRC_TYPE_LOGGING_CLIENT) {
irc_client_free(ic);
}
}
static void server_setup_reconnect_timer(struct link *link)
{
int timer = 0;
if (link->last_connection_attempt
&& time(NULL) - link->last_connection_attempt < CONN_INTERVAL) {
timer = conf_reconn_timer * (link->s_conn_attempt);
if (timer > RECONN_TIMER_MAX)
timer = RECONN_TIMER_MAX;
}
mylog(LOG_ERROR, "[%s] reconnecting in %d seconds", link->name, timer);
link->recon_timer = timer;
}
static void irc_close(struct link_any *l)
{
if (CONN(l)) {
connection_free(CONN(l));
CONN(l) = NULL;
}
if (TYPE(l) == IRC_TYPE_SERVER) {
/* TODO: free link_server as a whole */
struct link_server *is = (struct link_server *)l;
if (LINK(is)->s_state == IRCS_CONNECTED)
irc_notify_disconnection(is);
irc_server_shutdown(is);
log_disconnected(LINK(is)->log);
server_next(LINK(is));
server_cleanup(is);
server_setup_reconnect_timer(LINK(is));
LINK(is)->l_server = NULL;
irc_server_free((struct link_server *)is);
} else {
irc_client_close((struct link_client *)l);
}
}
struct link_client *irc_client_new(void)
{
struct link_client *c;
c = bip_calloc(sizeof(struct link_client), (size_t)1);
list_init(&c->who_queue, list_ptr_cmp);
return c;
}
struct link_server *irc_server_new(struct link *link, connection_t *conn)
{
struct link_server *s;
s = bip_calloc(sizeof(struct link_server), (size_t)1);
TYPE(s) = IRC_TYPE_SERVER;
hash_init(&s->channels, HASH_NOCASE);
link->l_server = s;
LINK(s) = link;
CONN(s) = conn;
irc_lag_init(s);
array_init(&s->chanmodes);
s->prefixes = NULL;
s->usermodes = NULL;
server_init_modes(s);
return s;
}
static void server_init_modes(struct link_server *s)
{
// Default values used if CHANMODES is not specified by the server
array_push(&s->chanmodes, bip_strdup("beHIq"));
array_push(&s->chanmodes, bip_strdup("k"));
array_push(&s->chanmodes, bip_strdup("fjl"));
array_push(&s->chanmodes, bip_strdup("fjl"));
// Default values used if prefix is not specified by the server
s->prefixes = bip_realloc(s->prefixes, sizeof(*s->prefixes) * 3);
s->usermodes = bip_realloc(s->usermodes, sizeof(s->usermodes) * 3);
strcpy(s->prefixes, "@+");
strcpy(s->usermodes, "ov");
}
void irc_server_free(struct link_server *s)
{
if (CONN(s))
connection_free(CONN(s));
if (s->nick)
free(s->nick);
if (s->user_mode)
free(s->user_mode);
int i;
char *ptr;
array_each(&s->chanmodes, i, ptr) free(ptr);
MAYFREE(s->prefixes);
MAYFREE(s->usermodes);
hash_iterator_t hi;
for (hash_it_init(&s->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
struct channel *chan = hash_it_item(&hi);
channel_free(chan);
}
hash_clean(&s->channels);
free(s);
}
connection_t *irc_server_connect(struct link *link)
{
struct link_server *ls;
connection_t *conn;
link->s_conn_attempt++;
mylog(LOG_INFO,
"[%s] Connecting user '%s' using server "
"%s:%d",
link->name, link->user->name,
link->network->serverv[link->cur_server].host,
link->network->serverv[link->cur_server].port);
conn = connection_new(link->network->serverv[link->cur_server].host,
link->network->serverv[link->cur_server].port,
link->vhost, link->bind_port,
#ifdef HAVE_LIBSSL
link->network->ssl, link->network->ciphers,
link->ssl_check_mode, link->user->ssl_check_store,
link->user->ssl_client_certfile,
#else
0, NULL, 0, NULL, NULL,
#endif
(time_t)CONNECT_TIMEOUT);
assert(conn);
if (conn->handle == -1) {
mylog(LOG_INFO, "[%s] Cannot connect.", link->name);
connection_free(conn);
server_next(link);
return NULL;
}
ls = irc_server_new(link, conn);
conn->user_data = ls;
list_add_last(&_bip->conn_list, conn);
oidentd_dump(_bip);
if (link->sasl_mechanism) {
mylog(LOG_INFO, "[%s] SASL (%s) enabled, sending CAP REQ.",
link->name, sasl_mechanism_to_text(link->sasl_mechanism));
WRITE_LINE2(conn, NULL, "CAP", "REQ", ":sasl");
}
irc_server_startup(ls);
return conn;
}
int irc_server_lag_compute(struct link *l)
{
struct link_server *server = l->l_server;
if (LINK(server)->s_state == IRCS_CONNECTED) {
if (server->laginit_ts != -1) {
irc_compute_lag(server);
if (!irc_lags_out(server))
return 0;
return 1;
} else {
server->lagtest_timeout--;
if (server->lagtest_timeout == 0)
irc_start_lagtest(server);
}
}
return 0;
}
void irc_server_shutdown(struct link_server *s)
{
int i;
char *cur;
array_each(&s->chanmodes, i, cur) free(cur);
array_deinit(&s->chanmodes);
server_init_modes(s);
if (!s->nick)
return;
if (LINK(s)->prev_nick)
free(LINK(s)->prev_nick);
LINK(s)->prev_nick = bip_strdup(s->nick);
}
#define BIP_OIDENTD_START "## AUTOGENERATED BY BIP. DO NOT EDIT ##\n"
#define BIP_OIDENTD_END "## END OF AUTOGENERATED STUFF ##\n"
#define BIP_OIDENTD_END_LENGTH strlen(BIP_OIDENTD_END)
void oidentd_dump(bip_t *bip)
{
mylog(LOG_ERROR, "%d %s", bip->write_oidentd, bip->oidentdpath);
if (!bip->write_oidentd || bip->oidentdpath == NULL) {
return;
}
list_iterator_t it;
FILE *f;
char *bipstart = NULL, *bipend = NULL;
struct stat stats;
char tag_written = 0;
if (stat(bip->oidentdpath, &stats) == -1) {
if (errno == ENOENT && (f = fopen(bip->oidentdpath, "w+"))) {
fchmod(fileno(f), 0644);
} else {
mylog(LOG_WARN, "Can't open/create %s",
bip->oidentdpath);
return;
}
} else {
/* strip previously autogenerated content */
char *content;
f = fopen(bip->oidentdpath, "r+");
if (!f) {
mylog(LOG_WARN, "Can't open/create %s",
bip->oidentdpath);
return;
}
// casting to size_t as stat should never return negative size
content = (char *)bip_malloc((size_t)(stats.st_size + 1));
// validate that content is of stats.st_size size
if (fread(content, (size_t)1, (size_t)stats.st_size, f)
!= (size_t)stats.st_size) {
mylog(LOG_WARN, "Can't read %s fully",
bip->oidentdpath);
free(content);
goto clean_oidentd;
}
/* Set terminating zero for strstr */
content[stats.st_size] = '\0';
bipstart = strstr(content, BIP_OIDENTD_START);
if (bipstart != NULL) {
// We have some config left, rewrite the file completely
fseek(f, (long)SEEK_SET, (int)0);
if (ftruncate(fileno(f), (off_t)0) == -1) {
mylog(LOG_DEBUG, "Can't reset %s size",
bip->oidentdpath);
free(content);
goto clean_oidentd;
}
// data preceeding the tag, bipstart >= content (strstr)
fwrite(content, (size_t)1, (size_t)(bipstart - content),
f);
bipend = strstr(bipstart, BIP_OIDENTD_END);
if (bipend == NULL) {
mylog(LOG_WARN, "No %s mark found in %s",
BIP_OIDENTD_END, bip->oidentdpath);
} else {
/* data following the tag
* ...........BIP_OIDENTD_START...BIP_OIDENTD_END..............
* ^content...^bipstart...........^bipend........^remaining
* data
*/
char *remaining =
bipend + BIP_OIDENTD_END_LENGTH;
off_t remaining_len =
stats.st_size - (bipend - content)
- (off_t)BIP_OIDENTD_END_LENGTH;
if (remaining_len < 0) {
mylog(LOG_ERROR,
"oidentd_dump: error parsing %s",
bip->oidentdpath);
goto clean_oidentd;
}
fwrite(remaining, (size_t)1,
(size_t)remaining_len, f);
}
} else {
/* No previous conf */
if (stats.st_size != 0
&& content[stats.st_size - 1] != '\n')
fprintf(f, "\n");
}
free(content);
}
for (list_it_init(&bip->conn_list, &it); list_it_item(&it);
list_it_next(&it)) {
connection_t *c = list_it_item(&it);
struct link_any *la = c->user_data;
if (la && TYPE(la) == IRC_TYPE_SERVER
&& (c->connected == CONN_OK
|| c->connected == CONN_NEED_SSLIZE
|| c->connected == CONN_UNTRUSTED)) {
struct link_server *ls;
struct link *l;
if (!tag_written) {
fprintf(f, BIP_OIDENTD_START);
tag_written = 1;
}
ls = (struct link_server *)la;
l = LINK(ls);
fprintf(f, "to %s fport %d from %s lport %d {\n",
c->remoteip, c->remoteport, c->localip,
c->localport);
fprintf(f, "\treply \"%s\"\n", l->username);
fprintf(f, "}\n");
}
}
if (tag_written)
fprintf(f, BIP_OIDENTD_END);
clean_oidentd:
fclose(f);
}
void timeout_clean_who_counts(list_t *conns)
{
list_iterator_t it;
for (list_it_init(conns, &it); list_it_item(&it); list_it_next(&it)) {
struct link *l = list_it_item(&it);
struct link_client *client = l->who_client;
if (client && client->whoc_tstamp) {
time_t now;
now = time(NULL);
if (now - client->whoc_tstamp > 10) {
mylog(LOG_DEBUG,
"Yawn, "
"forgetting one who reply");
if (client->who_count > 0)
--client->who_count;
client->whoc_tstamp = time(NULL);
if (client->who_count == 0)
rotate_who_client(l);
}
}
}
}
void bip_init(bip_t *bip)
{
memset(bip, 0, sizeof(bip_t));
list_init(&bip->link_list, list_ptr_cmp);
list_init(&bip->conn_list, list_ptr_cmp);
list_init(&bip->connecting_client_list, list_ptr_cmp);
hash_init(&bip->users, HASH_NOCASE);
hash_init(&bip->networks, HASH_NOCASE);
}
/* Called each second. */
void bip_tick(bip_t *bip)
{
static int logflush_timer = 0;
struct link *link;
list_iterator_t li;
/* log flushs */
if (logflush_timer-- <= 0) {
logflush_timer = conf_log_sync_interval;
log_flush_all();
}
/* handle tick for links: detect lags or start a reconnection */
for (list_it_init(&bip->link_list, &li); (link = list_it_item(&li));
list_it_next(&li)) {
if (link->l_server) {
if (irc_server_lag_compute(link)) {
log_ping_timeout(link->log);
list_remove(&bip->conn_list,
CONN(link->l_server));
irc_close((struct link_any *)link->l_server);
}
} else {
if (link->recon_timer == 0) {
connection_t *conn;
link->last_connection_attempt = time(NULL);
conn = irc_server_connect(link);
if (!conn)
server_setup_reconnect_timer(link);
} else {
link->recon_timer--;
}
}
}
/* drop lagging connecting client */
for (list_it_init(&bip->connecting_client_list, &li); list_it_item(&li);
list_it_next(&li)) {
struct link_client *ic = list_it_item(&li);
ic->logging_timer++;
if (ic->logging_timer > LOGGING_TIMEOUT) {
if (CONN(ic))
list_remove(&bip->conn_list, CONN(ic));
irc_close((struct link_any *)ic);
list_it_remove(&li);
}
}
/*
* Cleanup lagging or dangling who_count buffers
*/
timeout_clean_who_counts(&bip->link_list);
}
void bip_on_event(bip_t *bip, connection_t *conn)
{
struct link_any *lc = (struct link_any *)conn->user_data;
if (conn == bip->listener) {
struct link_client *n = irc_accept_new(conn);
if (n) {
list_add_last(&bip->conn_list, CONN(n));
list_add_last(&bip->connecting_client_list, n);
}
return;
}
/* reached only if socket is not listening */
int err;
list_t *linel = read_lines(conn, &err);
if (err) {
if (TYPE(lc) == IRC_TYPE_SERVER) {
mylog(LOG_ERROR, "[%s] read_lines error, closing...",
link_name(lc));
irc_server_shutdown(LINK(lc)->l_server);
} else {
mylog(LOG_ERROR, "client read_lines error, closing...");
}
goto prot_err;
}
if (!linel)
return;
char *line_s;
while ((line_s = list_remove_first(linel))) {
struct line *line;
mylog(LOG_DEBUG, "\"%s\"", line_s);
if (*line_s == 0) { /* irssi does that.*/
free(line_s);
continue;
}
line = irc_line_new_from_string(line_s);
if (!line) {
mylog(LOG_ERROR,
"[%s] Can not parse line. Link type: %d. "
"closing...",
link_name(lc), TYPE(lc));
free(line_s);
goto prot_err_lines;
}
int r;
r = irc_dispatch(bip, lc, line);
irc_line_free(line);
free(line_s);
if (r == ERR_PROTOCOL) {
mylog(LOG_ERROR,
"[%s] Error in protocol. Link type: %d closing...",
link_name(lc), TYPE(lc));
goto prot_err_lines;
}
if (r == ERR_AUTH)
goto prot_err_lines;
/* XXX: not real error */
if (r == OK_CLOSE)
goto prot_err_lines;
}
list_free(linel);
return;
prot_err_lines:
while ((line_s = list_remove_first(linel)))
free(line_s);
prot_err:
list_remove(&bip->conn_list, conn);
if (linel)
list_free(linel);
if (lc) {
if (TYPE(lc) == IRC_TYPE_LOGGING_CLIENT
|| TYPE(lc) == IRC_TYPE_TRUST_CLIENT)
list_remove(&bip->connecting_client_list, lc);
irc_close(lc);
}
}
/*
* The main loop
* inc is the incoming connection, clientl list a list of client struct that
* represent the accepcted credentials
*/
void irc_main(bip_t *bip)
{
time_t timeleft = 1000;
if (bip->reloading_client) {
char *l;
while ((l = list_remove_first(&bip->errors)))
bip_notify(bip->reloading_client, "%s", l);
bip->reloading_client = NULL;
}
/*
* If the list is empty, we are starting. Otherwise we are reloading,
* and conn_list is kept accross reloads.
*/
if (list_is_empty(&bip->conn_list))
list_add_first(&bip->conn_list, bip->listener);
while (!sighup) {
connection_t *conn;
if (timeleft == 0) {
/*
* Compute timeouts for next reconnections and lagouts
*/
timeleft = 1000;
bip_tick(bip);
}
int nc;
/* Da main loop */
list_t *ready = wait_event(&bip->conn_list, &timeleft, &nc);
if (nc)
oidentd_dump(bip);
while ((conn = list_remove_first(ready)))
bip_on_event(bip, conn);
list_free(ready);
}
while (list_remove_first(&bip->connecting_client_list))
;
return;
}
void irc_client_free(struct link_client *cli)
{
if (CONN(cli))
connection_free(CONN(cli));
if (cli->init_pass)
free(cli->init_pass);
if (cli->init_nick)
free(cli->init_nick);
free(cli);
}
struct link *irc_link_new(void)
{
struct link *link;
link = bip_calloc(sizeof(struct link), (size_t)1);
link->l_server = NULL;
hash_init(&link->chan_infos, HASH_NOCASE);
list_init(&link->chan_infos_order, list_ptr_cmp);
list_init(&link->on_connect_send, list_ptr_cmp);
link->autojoin_on_kick = 1;
link->ignore_server_capab = 1;
return link;
}
void link_kill(bip_t *bip, struct link *link)
{
/* in case in never got connected */
if (link->l_server) {
list_remove(&bip->conn_list, CONN(link->l_server));
server_cleanup(link->l_server);
irc_server_free(link->l_server);
}
while (link->l_clientc) {
struct link_client *lc = link->l_clientv[0];
if (lc == bip->reloading_client)
bip->reloading_client = NULL;
list_remove(&bip->conn_list, CONN(lc));
unbind_from_link(lc);
irc_client_free(lc);
}
hash_remove(&link->user->connections, link->name);
free(link->name);
log_free(link->log);
MAYFREE(link->prev_nick);
MAYFREE(link->cli_nick);
void *p;
while ((p = list_remove_first(&link->init_strings)))
free(p);
while ((p = list_remove_first(&link->on_connect_send)))
free(p);
MAYFREE(link->no_client_away_msg);
MAYFREE(link->away_nick);
hash_clean(&link->chan_infos);
struct chan_infos *ci;
while ((ci = list_remove_first(&link->chan_infos_order)))
free(ci);
list_remove(&bip->link_list, link);
MAYFREE(link->username);
MAYFREE(link->realname);
MAYFREE(link->s_password);
MAYFREE(link->sasl_username);
MAYFREE(link->sasl_password);
MAYFREE(link->connect_nick);
MAYFREE(link->vhost);
#ifdef HAVE_LIBSSL
sk_X509_free(link->untrusted_certs);
#endif
free(link);
}
static void server_set_chanmodes(struct link_server *l, const char *modes)
{
int i;
char *cur;
char *dup;
mylog(LOG_DEBUG, "[%s] Set chanmodes", LINK(l)->name);
array_each(&l->chanmodes, i, cur) free(cur);
array_deinit(&l->chanmodes);
// handle four categories, ignore all others
for (i = 0; i < 4; i++) {
cur = strchr(modes, ',');
if (cur || modes) {
size_t len;
if (cur)
// cur can't be lower than modes if !NULL
len = (size_t)(cur - modes);
else
len = strlen(modes); // last piece
dup = bip_malloc(len + 1);
memcpy(dup, modes, len);
dup[len] = 0;
modes = cur + 1;
} else {
// emptry string
dup = bip_calloc((size_t)1, sizeof(char));
}
mylog(LOG_DEBUGVERB, "[%s] Modes: '%s'", LINK(l)->name, dup);
array_push(&l->chanmodes, dup);
}
}
static void server_set_prefix(struct link_server *s, const char *modes)
{
char *end_mode;
size_t len;
mylog(LOG_DEBUG, "[%s] Set user modes", LINK(s)->name);
// PREFIX=(mode)prefix
end_mode = strchr(modes + 1, ')'); // skip '('
if (*modes != '(' || !end_mode) {
mylog(LOG_WARN, "[%s] Unable to parse PREFIX parameter",
LINK(s)->name);
return;
}
// end_mode can't be lower than (modes + 1)
len = (size_t)(end_mode - modes - 1); // len of mode without '('
if (len * 2 + 2 != strlen(modes)) {
mylog(LOG_WARN, "[%s] Unable to parse PREFIX parameter",
LINK(s)->name);
return;
}
s->prefixes =
bip_realloc(s->prefixes, sizeof(*s->prefixes) * (len + 1));
s->usermodes =
bip_realloc(s->usermodes, sizeof(s->usermodes) * (len + 1));
memcpy(s->usermodes, modes + 1, len);
s->usermodes[len] = 0;
memcpy(s->prefixes, end_mode + 1, len);
s->prefixes[len] = 0;
mylog(LOG_DEBUGVERB, "[%s] user prefix: '%s'", LINK(s)->name,
s->prefixes);
mylog(LOG_DEBUGVERB, "[%s] user modes: '%s'", LINK(s)->name,
s->usermodes);
}
// Return the position (*1 based*) of car in str, else -1
static int bip_get_index(const char *str, char car)
{
char *cur;
long diff;
if (!(cur = strchr(str, car)))
return 0;
diff = cur - str + 1;
if (diff > INT_MAX)
fatal("bip_get_index: string too long");
return (int)diff;
}
static int bip_fls(long v)
{
int r = 0;
while (v >>= 1)
r++;
return r;
}