bip/src/irc.c

3042 lines
76 KiB
C
Raw Normal View History

2005-04-28 10:26:44 +02:00
/*
* $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
2005-04-28 10:26:44 +02:00
*
* 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"
2005-08-25 10:17:10 +02:00
#include "bip.h"
2005-04-28 10:26:44 +02:00
#include "log.h"
#include "connection.h"
#include "md5.h"
#include "utils/base64.h"
2005-04-28 10:26:44 +02:00
// TODO resolve assuming signed overflow does not occur when changing X +- C1
// cmp C2 to X cmp C2 -+ C1
2022-01-09 21:30:25 +01:00
#pragma GCC diagnostic ignored "-Wstrict-overflow"
2005-05-24 01:05:11 +02:00
#define S_CONN_DELAY (10)
2005-04-28 10:26:44 +02:00
extern int sighup;
extern bip_t *_bip;
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
void irc_server_shutdown(struct link_server *s);
2007-01-26 19:52:21 +01:00
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);
2005-04-28 10:26:44 +02:00
void irc_client_free(struct link_client *cli);
extern int conf_log_sync_interval;
extern int conf_reconn_timer;
2005-04-28 10:26:44 +02:00
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);
2007-09-13 17:24:57 +02:00
#define LAGOUT_TIME 480
2005-04-28 10:26:44 +02:00
#define LAGCHECK_TIME (90)
#define RECONN_TIMER_MAX (600)
2005-04-28 10:26:44 +02:00
#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);
2005-04-28 10:26:44 +02:00
return chan;
}
char *nick_from_ircmask(const char *mask)
2005-04-28 10:26:44 +02:00
{
const char *nick = mask;
2005-04-28 10:26:44 +02:00
char *ret;
size_t len;
2022-01-09 20:39:38 +01:00
if (!mask)
return NULL;
2006-06-20 13:31:22 +02:00
2005-04-28 10:26:44 +02:00
while (*nick && *nick != '!')
nick++;
if (!*nick)
return bip_strdup(mask);
len = (size_t)(nick - mask); // cannot be < 0
2008-12-10 23:26:37 +01:00
ret = bip_malloc(len + 1);
2005-04-28 10:26:44 +02:00
memcpy(ret, mask, len);
ret[len] = 0;
return ret;
}
#define NAMESIZE 256
list_t *channel_name_list(struct link_server *server, struct channel *c)
2005-04-28 10:26:44 +02:00
{
list_t *ret;
hash_iterator_t hi;
size_t len = 0;
char *str = bip_malloc((size_t)(NAMESIZE + 1));
ret = list_new(NULL);
2005-04-28 10:26:44 +02:00
*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);
2005-04-28 10:26:44 +02:00
assert(strlen(nick) + 2 < NAMESIZE);
2005-04-28 10:26:44 +02:00
if (len + strlen(nick) + 2 + (ovmask ? 1 : 0) >= NAMESIZE) {
2005-04-28 10:26:44 +02:00
list_add_last(ret, str);
str = bip_malloc((size_t)(NAMESIZE + 1));
2005-04-28 10:26:44 +02:00
*str = 0;
len = 0;
}
if (len != 0) {
strcat(str, " ");
2005-04-28 10:26:44 +02:00
len++;
}
// prepend symbol corresponding to the usermode
int msb;
2018-11-18 15:10:16 +01:00
if ((msb = bip_fls(ovmask))) {
str[len] = server->prefixes[msb - 1];
str[++len] = 0;
}
strcat(str, nick);
len += strlen(nick);
assert(len < NAMESIZE);
2005-04-28 10:26:44 +02:00
}
list_add_last(ret, str);
return ret;
}
char *link_name(struct link_any *l)
{
if (LINK(l))
2010-09-12 18:32:55 +02:00
return LINK(l)->name ? LINK(l)->name : "(null)";
return "*connecting*";
}
2005-04-28 10:26:44 +02:00
static int irc_001(struct link_server *server, struct line *line)
{
2007-01-26 19:52:21 +01:00
(void)line;
2005-04-28 10:26:44 +02:00
if (LINK(server)->s_state == IRCS_WAS_CONNECTED)
LINK(server)->s_state = IRCS_RECONNECTING;
else
2005-04-28 10:26:44 +02:00
LINK(server)->s_state = IRCS_CONNECTING;
2007-01-26 19:52:21 +01:00
/* change nick on client */
unsigned int i;
2007-01-26 19:52:21 +01:00
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);
2005-04-28 10:26:44 +02:00
}
2007-01-26 19:52:21 +01:00
return OK_COPY;
2005-04-28 10:26:44 +02:00
}
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;
2009-01-10 12:35:59 +01:00
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;
2005-04-28 10:26:44 +02:00
}
int irc_lags_out(struct link_server *is)
{
if (is->lag > LAGOUT_TIME) {
2009-07-05 19:04:51 +02:00
mylog(LOG_ERROR, "[%s] Lags out! closing", LINK(is)->name);
2005-04-28 10:26:44 +02:00
return 1;
} else {
2009-07-05 19:04:51 +02:00
mylog(LOG_DEBUG, "[%s] lag : %d\n", LINK(is)->name, is->lag);
2005-04-28 10:26:44 +02:00
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)
{
2005-05-30 20:35:49 +02:00
list_iterator_t it;
for (list_it_init(&LINK(s)->chan_infos_order, &it); list_it_item(&it);
list_it_next(&it)) {
2005-05-30 20:35:49 +02:00
struct chan_info *ci = list_it_item(&it);
2005-04-28 10:26:44 +02:00
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;
2007-02-10 15:05:37 +01:00
mylog(LOG_INFO, "[%s] Connected for user %s", LINK(server)->name,
LINK(server)->user->name);
2007-02-10 15:05:37 +01:00
irc_server_join(server);
log_connected(LINK(server)->log);
2005-04-28 10:26:44 +02:00
2007-01-26 19:52:21 +01:00
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);
2007-01-26 19:52:21 +01:00
}
free(LINK(server)->cli_nick);
LINK(server)->cli_nick = NULL;
}
/* basic helper for nickserv and co */
2006-09-18 20:05:17 +02:00
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);
}
2006-09-25 14:08:31 +02:00
if (LINK(server)->l_clientc == 0) {
if (LINK(server)->away_nick)
WRITE_LINE1(CONN(server), NULL, "NICK",
LINK(server)->away_nick);
2006-09-25 14:08:31 +02:00
if (LINK(server)->no_client_away_msg)
WRITE_LINE1(CONN(server), NULL, "AWAY",
LINK(server)->no_client_away_msg);
2006-09-25 14:08:31 +02:00
}
2005-04-28 10:26:44 +02:00
}
/*
2007-01-26 19:52:21 +01:00
* Given the way irc nets disrespect the rfc, we completely forget
2005-04-28 10:26:44 +02:00
* 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
2005-04-28 10:26:44 +02:00
*/
static int irc_352(struct link_server *server, struct line *line)
2005-04-28 10:26:44 +02:00
{
2007-01-26 19:52:21 +01:00
(void)server;
if (!irc_line_includes(line, 6))
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
2005-11-16 19:34:25 +01:00
#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
2007-01-26 19:52:21 +01:00
if (!origin_is_me(line, server)) {
2005-11-16 19:34:25 +01:00
struct channel *channel;
struct nick *nick;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
2005-11-16 19:34:25 +01:00
if (!channel)
2007-01-26 19:52:21 +01:00
return OK_COPY_WHO;
2005-11-16 19:34:25 +01:00
nick = hash_get(&channel->nicks, irc_line_elem(line, 6));
2005-11-16 19:34:25 +01:00
if (!nick)
2007-01-26 19:52:21 +01:00
return OK_COPY_WHO;
2005-11-16 19:34:25 +01:00
}
2007-01-26 19:52:21 +01:00
#endif
return OK_COPY_WHO;
}
2018-11-18 15:10:16 +01:00
static int irc_315(struct link_server *server, struct line *l)
{
2018-11-18 15:10:16 +01:00
(void)l;
2006-02-21 08:48:12 +01:00
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;
2007-02-08 23:53:48 +01:00
mylog(LOG_DEBUG,
"RPL_ENDOFWHO: "
"Decrementing who count for %p: %d",
link->who_client, link->who_client->who_count);
}
}
return OK_COPY_WHO;
2005-04-28 10:26:44 +02:00
}
void rotate_who_client(struct link *link)
{
unsigned int i;
2007-02-08 23:53:48 +01:00
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;
}
}
}
2007-05-26 12:31:44 +02:00
int irc_dispatch_server(bip_t *bip, struct link_server *server,
struct line *line)
2005-04-28 10:26:44 +02:00
{
int ret = OK_COPY;
2007-05-26 12:31:44 +02:00
/* shut gcc up */
(void)bip;
2005-04-28 10:26:44 +02:00
if (!irc_line_includes(line, 0))
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if (irc_line_elem_equals(line, 0, "PING")) {
if (!irc_line_includes(line, 1))
2005-04-28 10:26:44 +02:00
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));
2005-05-26 17:36:15 +02:00
resp->colon = 1; /* it seems some ircds want it */
2005-04-28 10:26:44 +02:00
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) {
2005-04-28 10:26:44 +02:00
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")) {
2005-04-28 10:26:44 +02:00
if (LINK(server)->s_state != IRCS_CONNECTED) {
size_t nicklen = strlen(server->nick);
2008-12-10 23:26:37 +01:00
char *newnick = bip_malloc(nicklen + 2);
2005-04-28 10:26:44 +02:00
strcpy(newnick, server->nick);
2007-11-15 23:29:10 +01:00
if (strlen(server->nick) < 9) {
2005-04-28 10:26:44 +02:00
strcat(newnick, "`");
} else {
if (newnick[7] != '`') {
if (newnick[8] != '`') {
newnick[8] = '`';
} else {
newnick[7] = '`';
}
2007-11-15 23:29:10 +01:00
} else {
newnick[8] =
(char)('a'
+ ('z' - 'a') * rand()
/ RAND_MAX);
2007-11-15 23:29:10 +01:00
}
2005-04-28 10:26:44 +02:00
newnick[9] = 0;
}
ls_set_nick(server, newnick);
2005-04-28 10:26:44 +02:00
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 */
2005-04-28 10:26:44 +02:00
irc_server_connected(server);
else if (irc_line_elem_equals(line, 0, "422")) /* no motd */
irc_server_connected(server);
2005-04-28 10:26:44 +02:00
} 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 */
2005-04-28 10:26:44 +02:00
irc_server_connected(server);
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
2005-04-28 10:26:44 +02:00
} else {
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
2005-04-28 10:26:44 +02:00
}
} else if (irc_line_elem_equals(line, 0, "001")) {
2005-04-28 10:26:44 +02:00
ret = irc_001(server, line);
if (LINK(server)->s_state == IRCS_CONNECTING) {
if (!list_is_empty(&LINK(server)->init_strings))
return ERR_PROTOCOL;
2007-01-26 19:52:21 +01:00
/* update the irc mask */
2005-04-28 10:26:44 +02:00
list_add_last(&LINK(server)->init_strings,
irc_line_dup(line));
2005-04-28 10:26:44 +02:00
}
} else if (irc_line_elem_equals(line, 0, "JOIN")) {
2005-04-28 10:26:44 +02:00
ret = irc_join(server, line);
} else if (irc_line_elem_equals(line, 0, "332")) {
2005-04-28 10:26:44 +02:00
ret = irc_332(server, line);
} else if (irc_line_elem_equals(line, 0, "333")) {
2005-04-28 10:26:44 +02:00
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")) {
2005-04-28 10:26:44 +02:00
ret = irc_353(server, line);
} else if (irc_line_elem_equals(line, 0, "366")) {
2005-04-28 10:26:44 +02:00
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")) {
2005-04-28 10:26:44 +02:00
ret = irc_part(server, line);
} else if (irc_line_elem_equals(line, 0, "MODE")) {
2005-04-28 10:26:44 +02:00
ret = irc_mode(server, line);
} else if (irc_line_elem_equals(line, 0, "TOPIC")) {
2005-04-28 10:26:44 +02:00
ret = irc_topic(server, line);
} else if (irc_line_elem_equals(line, 0, "KICK")) {
2005-04-28 10:26:44 +02:00
ret = irc_kick(server, line);
} else if (irc_line_elem_equals(line, 0, "PRIVMSG")) {
2005-04-28 10:26:44 +02:00
ret = irc_privmsg(server, line);
} else if (irc_line_elem_equals(line, 0, "NOTICE")) {
2005-04-28 10:26:44 +02:00
ret = irc_notice(server, line);
} else if (irc_line_elem_equals(line, 0, "QUIT")) {
2005-04-28 10:26:44 +02:00
ret = irc_quit(server, line);
} else if (irc_line_elem_equals(line, 0, "NICK")) {
2005-04-28 10:26:44 +02:00
ret = irc_nick(server, line);
}
if (ret == OK_COPY) {
unsigned int i;
2005-04-28 10:26:44 +02:00
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);
}
2005-04-28 10:26:44 +02:00
}
}
2006-02-21 08:48:12 +01:00
if (ret == OK_COPY_WHO && LINK(server)->who_client) {
char *s;
s = irc_line_to_string(line);
2006-02-21 08:48:12 +01:00
write_line(CONN(LINK(server)->who_client), s);
free(s);
}
if (LINK(server)->who_client
&& LINK(server)->who_client->who_count == 0) {
2007-02-08 23:53:48 +01:00
mylog(LOG_DEBUG, "OK_COPY_WHO: who_count for %p is nul",
LINK(server)->who_client);
rotate_who_client(LINK(server));
}
2005-04-28 10:26:44 +02:00
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;
2009-01-10 12:35:59 +01:00
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);
2005-04-28 10:26:44 +02:00
if (chan->topic)
WRITE_LINE3(CONN(ic), P_SERV, "332", LINK(ic)->l_server->nick,
chan->name, chan->topic);
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
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.");
2005-04-28 10:26:44 +02:00
}
static void write_init_string(connection_t *c, struct line *line, char *nick)
{
char *l;
l = irc_line_to_string_to(line, nick);
2005-04-28 10:26:44 +02:00
write_line(c, l);
free(l);
}
static void bind_to_link(struct link *l, struct link_client *ic)
{
unsigned int i = l->l_clientc;
2005-04-28 10:26:44 +02:00
LINK(ic) = l;
l->l_clientc++;
l->l_clientv = bip_realloc(l->l_clientv,
l->l_clientc * sizeof(struct link_client *));
2005-04-28 10:26:44 +02:00
l->l_clientv[i] = ic;
}
2005-08-25 10:17:10 +02:00
void unbind_from_link(struct link_client *ic)
2005-04-28 10:26:44 +02:00
{
struct link *l = LINK(ic);
unsigned int i;
2006-02-21 08:48:12 +01:00
2005-04-28 10:26:44 +02:00
for (i = 0; i < l->l_clientc; i++)
if (l->l_clientv[i] == ic)
break;
2009-01-10 12:35:59 +01:00
assert(i != l->l_clientc);
2006-02-21 08:48:12 +01:00
if (l->who_client == ic) {
2007-05-26 12:31:44 +02:00
mylog(LOG_DEBUG, "unbind_from_link: %p: %d", l->who_client,
ic->who_count);
2007-05-26 12:31:44 +02:00
l->who_client = NULL;
}
2006-02-21 08:48:12 +01:00
2005-04-28 10:26:44 +02:00
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");
2005-04-28 10:26:44 +02:00
l->l_clientc--;
l->l_clientv = bip_realloc(l->l_clientv,
l->l_clientc * sizeof(struct link_client *));
2008-12-11 11:00:05 +01:00
if (l->l_clientc == 0) { /* bip_realloc was equiv to free() */
2005-04-28 10:26:44 +02:00
l->l_clientv = NULL;
return;
}
}
int irc_cli_bip(bip_t *bip, struct link_client *ic, struct line *line)
2005-04-28 10:26:44 +02:00
{
return adm_bip(bip, ic, line, 0);
2005-04-28 10:26:44 +02:00
}
2005-07-09 14:55:01 +02:00
#define PASS_SEP ':'
static char *get_str_elem(char *str, int num)
{
char *ret;
char *c;
char *cur = str;
int index = 0;
2005-08-01 13:16:39 +02:00
while ((c = strchr(cur, PASS_SEP))) {
long len = c - cur;
2005-07-09 14:55:01 +02:00
if (index < num) {
index++;
cur = c + 1;
continue;
}
if (len < 1)
2005-07-09 14:55:01 +02:00
return NULL;
// len always > 0
ret = bip_malloc((size_t)len + 1);
memcpy(ret, cur, (size_t)len);
ret[len] = 0;
2005-07-09 14:55:01 +02:00
return ret;
}
if (index == num) {
long len;
2005-07-09 14:55:01 +02:00
c = str + strlen(str);
len = c - cur;
if (len < 1)
2005-07-09 14:55:01 +02:00
return NULL;
ret = bip_malloc((size_t)len + 1);
memcpy(ret, cur, (size_t)len);
ret[len] = 0;
2005-07-09 14:55:01 +02:00
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;
2009-01-10 12:35:59 +01:00
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)) {
2009-07-05 19:04:51 +02:00
mylog(LOG_INFO, "[%s] backlogging: %s",
LINK(ic)->name, bl);
write_lines(CONN(ic), bllines);
}
list_free(bllines);
}
free(bl);
}
list_free(backlogl);
}
2007-05-26 12:31:44 +02:00
static int irc_cli_startup(bip_t *bip, struct link_client *ic,
struct line *line)
2005-04-28 10:26:44 +02:00
{
2005-07-09 14:55:01 +02:00
char *init_nick;
char *user, *pass, *connname;
2005-08-01 13:24:10 +02:00
(void)line;
2005-04-28 10:26:44 +02:00
2009-01-10 12:35:59 +01:00
assert(ic->init_pass);
2005-04-28 10:26:44 +02:00
2005-07-09 14:55:01 +02:00
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;
}
2005-04-28 10:26:44 +02:00
list_iterator_t it;
2007-05-26 12:31:44 +02:00
for (list_it_init(&bip->link_list, &it); list_it_item(&it);
list_it_next(&it)) {
2005-04-28 10:26:44 +02:00
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) {
2005-07-09 14:55:01 +02:00
bind_to_link(l, ic);
break;
}
2005-04-28 10:26:44 +02:00
}
}
2005-05-16 00:46:28 +02:00
if (!LINK(ic))
2009-07-05 19:04:51 +02:00
mylog(LOG_ERROR, "[%s] Invalid credentials (user: %s)",
connname, user);
2005-07-09 14:55:01 +02:00
free(user);
free(connname);
free(pass);
2005-04-28 10:26:44 +02:00
free(ic->init_pass);
ic->init_pass = NULL;
init_nick = ic->init_nick;
ic->init_nick = NULL;
2005-05-21 15:44:09 +02:00
if (!LINK(ic)) {
free(init_nick);
2005-04-28 10:26:44 +02:00
return ERR_AUTH;
2005-05-21 15:44:09 +02:00
}
2005-04-28 10:26:44 +02:00
2005-08-25 10:17:10 +02:00
#ifdef HAVE_LIBSSL
2005-08-27 10:24:55 +02:00
if (LINK(ic)->s_state != IRCS_CONNECTED) {
2005-08-25 10:17:10 +02:00
/* Check if we have an untrusted certificate from the server */
if (ssl_check_trust(ic)) {
free(init_nick);
return OK_FORGET;
}
}
#endif
2005-08-25 10:17:10 +02:00
2005-04-28 10:26:44 +02:00
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 "
2005-04-28 10:26:44 +02:00
":ERROR Proxy not yet connected, try again "
"later\r\n");
unbind_from_link(ic);
2005-05-21 15:44:09 +02:00
free(init_nick);
2005-04-28 10:26:44 +02:00
return OK_CLOSE;
}
2007-05-26 12:31:44 +02:00
list_remove(&bip->connecting_client_list, ic);
2005-04-28 10:26:44 +02:00
TYPE(ic) = IRC_TYPE_CLIENT;
for (list_it_init(&LINK(ic)->init_strings, &it); list_it_item(&it);
list_it_next(&it))
2005-04-28 10:26:44 +02:00
write_init_string(CONN(ic), list_it_item(&it), init_nick);
2006-09-18 18:06:23 +02:00
/* we change nick on server */
2005-04-28 10:26:44 +02:00
if (LINK(ic)->l_server) {
struct link_server *server = LINK(ic)->l_server;
2007-01-26 19:52:21 +01:00
WRITE_LINE1(CONN(ic), init_nick, "NICK", server->nick);
2005-04-28 10:26:44 +02:00
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)
2005-04-28 10:26:44 +02:00
WRITE_LINE1(CONN(server), NULL, "NICK",
LINK(server)->connect_nick);
2006-09-18 18:06:23 +02:00
/* change away status */
2006-09-25 14:08:31 +02:00
if (server && LINK(ic)->no_client_away_msg)
WRITE_LINE0(CONN(server), NULL, "AWAY");
2005-04-28 10:26:44 +02:00
}
if (!LINK(ic)->l_server) {
free(init_nick);
return OK_FORGET;
}
irc_cli_make_join(ic);
irc_cli_backlog(ic, 0);
2005-04-28 10:26:44 +02:00
log_client_connected(LINK(ic)->log);
free(init_nick);
return OK_FORGET;
}
2007-05-26 12:31:44 +02:00
static int irc_cli_nick(bip_t *bip, struct link_client *ic, struct line *line)
2005-04-28 10:26:44 +02:00
{
if (irc_line_count(line) != 2)
2005-04-28 10:26:44 +02:00
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));
2005-04-28 10:26:44 +02:00
if ((ic->state & IRCC_READY) == IRCC_READY)
2007-05-26 12:31:44 +02:00
return irc_cli_startup(bip, ic, line);
2005-04-28 10:26:44 +02:00
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");
2005-04-28 10:26:44 +02:00
return OK_FORGET;
}
2007-05-26 12:31:44 +02:00
static int irc_cli_user(bip_t *bip, struct link_client *ic, struct line *line)
2005-04-28 10:26:44 +02:00
{
if (irc_line_count(line) != 5)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if ((ic->state & IRCC_READY) == IRCC_READY)
return ERR_PROTOCOL;
ic->state |= IRCC_USER;
if ((ic->state & IRCC_READY) == IRCC_READY)
2007-05-26 12:31:44 +02:00
return irc_cli_startup(bip, ic, line);
2005-04-28 10:26:44 +02:00
return OK_FORGET;
}
2007-05-26 12:31:44 +02:00
static int irc_cli_pass(bip_t *bip, struct link_client *ic, struct line *line)
2005-04-28 10:26:44 +02:00
{
if (irc_line_count(line) != 2)
2005-04-28 10:26:44 +02:00
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));
2005-04-28 10:26:44 +02:00
if ((ic->state & IRCC_READY) == IRCC_READY)
2007-05-26 12:31:44 +02:00
return irc_cli_startup(bip, ic, line);
2005-04-28 10:26:44 +02:00
return OK_FORGET;
}
static int irc_cli_quit(struct link_client *ic, struct line *line)
{
2005-08-01 13:24:10 +02:00
(void)ic;
(void)line;
2005-04-28 10:26:44 +02:00
return OK_CLOSE;
}
static int irc_cli_privmsg(bip_t *bip, struct link_client *ic,
struct line *line)
2005-04-28 10:26:44 +02:00
{
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));
}
2005-04-28 10:26:44 +02:00
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));
}
2005-04-28 10:26:44 +02:00
return OK_COPY_CLI;
}
static int irc_cli_who(struct link_client *ic, struct line *line)
{
2006-02-21 08:48:12 +01:00
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);
2006-02-21 08:48:12 +01:00
if (l->who_client && l->who_client != ic) {
list_add_first(&ic->who_queue, irc_line_to_string(line));
return OK_FORGET;
}
2006-02-21 08:48:12 +01:00
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);
2007-02-08 23:53:48 +01:00
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;
}
2005-04-28 10:26:44 +02:00
static void irc_notify_disconnection(struct link_server *is)
{
unsigned int i;
LINK(is)->cli_nick = bip_strdup(is->nick);
2007-01-26 19:52:21 +01:00
2005-04-28 10:26:44 +02:00
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)) {
2005-04-28 10:26:44 +02:00
struct channel *c = (struct channel *)hash_it_item(&hi);
WRITE_LINE3(CONN(ic), P_IRCMASK, "KICK", c->name,
is->nick,
"Server disconnected, reconnecting");
2005-04-28 10:26:44 +02:00
}
2007-09-28 10:43:34 +02:00
bip_notify(ic, "Server disconnected, reconnecting");
2005-04-28 10:26:44 +02:00
}
}
void irc_add_channel_info(struct link_server *ircs, const char *chan,
const char *key)
2005-04-28 10:26:44 +02:00
{
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"
2005-04-28 10:26:44 +02:00
if (!ischannel(*chan))
return;
#pragma GCC diagnostic pop
2005-04-28 10:26:44 +02:00
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;
2007-11-26 22:58:10 +01:00
ci->backlog = 1;
2005-04-28 10:26:44 +02:00
hash_insert(&LINK(ircs)->chan_infos, chan, ci);
2005-05-30 20:35:49 +02:00
list_add_last(&LINK(ircs)->chan_infos_order, ci);
2005-04-28 10:26:44 +02:00
} else {
if (ci->key) {
free(ci->key);
ci->key = NULL;
}
ci->key = key ? bip_strdup(key) : NULL;
2005-04-28 10:26:44 +02:00
}
}
static int irc_cli_join(struct link_client *irc, struct line *line)
{
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
2009-01-17 15:03:06 +01:00
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);
2005-04-28 10:26:44 +02:00
else
ks = NULL;
while ((e = strchr(s, ','))) {
size_t len = (size_t)(e - s); // cannot be < 0 or NULL per while
2008-12-10 23:26:37 +01:00
char *p = bip_malloc(len + 1);
2005-04-28 10:26:44 +02:00
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);
2008-12-10 23:26:37 +01:00
kp = bip_malloc(klen + 1);
2005-04-28 10:26:44 +02:00
memcpy(kp, ks, klen);
kp[klen] = 0;
if (*ke == 0)
ks = NULL;
} else {
kp = NULL;
ks = NULL;
}
2005-04-28 10:26:44 +02:00
}
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)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
cname = irc_line_elem(line, 1);
if ((ci = hash_remove_if_exists(&LINK(irc)->chan_infos, cname))
!= NULL) {
2005-05-30 20:35:49 +02:00
list_remove(&LINK(irc)->chan_infos_order, ci);
2005-04-28 10:26:44 +02:00
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
2007-05-26 12:31:44 +02:00
static int irc_dispatch_client(bip_t *bip, struct link_client *ic,
struct line *line)
2005-04-28 10:26:44 +02:00
{
int r = OK_COPY;
if (irc_line_count(line) == 0)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if (irc_line_elem_equals(line, 0, "PING")) {
if (!irc_line_includes(line, 1))
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
WRITE_LINE1(CONN(ic), link_name((struct link_any *)ic), "PONG",
irc_line_elem(line, 1));
2005-04-28 10:26:44 +02:00
r = OK_FORGET;
2005-08-04 14:40:09 +02:00
} else if (LINK(ic)->s_state != IRCS_CONNECTED) {
write_line_fast(CONN(ic),
":irc.bip.net NOTICE pouet "
2005-08-04 14:40:09 +02:00
":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")) {
2005-04-28 10:26:44 +02:00
r = irc_cli_join(ic, line);
} else if (irc_line_elem_equals(line, 0, "PART")) {
2005-04-28 10:26:44 +02:00
r = irc_cli_part(ic, line);
} else if (irc_line_elem_equals(line, 0, "NICK")) {
2007-05-26 12:31:44 +02:00
r = irc_cli_nick(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "QUIT")) {
2005-04-28 10:26:44 +02:00
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")) {
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
}
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)
2005-04-28 10:26:44 +02:00
write_line(CONN(LINK(ic)->l_server), str);
2007-01-26 19:52:21 +01:00
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");
2005-04-28 10:26:44 +02:00
free(str);
if (r == OK_COPY_CLI) {
unsigned int i;
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
}
}
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)) {
2009-01-10 12:35:59 +01:00
assert(!line->origin);
2007-01-26 19:52:21 +01:00
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;
2008-12-10 23:26:37 +01:00
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;
}
2021-10-07 18:04:17 +02:00
static int irc_dispatch_logging_client(bip_t *bip, struct link_client *ic,
struct line *line)
2005-04-28 10:26:44 +02:00
{
if (irc_line_count(line) == 0)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if (irc_line_elem_equals(line, 0, "NICK")) {
2007-05-26 12:31:44 +02:00
return irc_cli_nick(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "USER")) {
2007-05-26 12:31:44 +02:00
return irc_cli_user(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "PASS")) {
2007-05-26 12:31:44 +02:00
return irc_cli_pass(bip, ic, line);
2005-04-28 10:26:44 +02:00
}
return OK_FORGET;
}
2007-05-26 12:31:44 +02:00
int irc_dispatch(bip_t *bip, struct link_any *l, struct line *line)
2005-04-28 10:26:44 +02:00
{
switch (TYPE(l)) {
case IRC_TYPE_SERVER:
return irc_dispatch_server(bip, (struct link_server *)l, line);
2005-04-28 10:26:44 +02:00
break;
case IRC_TYPE_CLIENT:
return irc_dispatch_client(bip, (struct link_client *)l, line);
2005-04-28 10:26:44 +02:00
break;
2021-10-07 18:04:17 +02:00
case IRC_TYPE_LOGGING_CLIENT:
return irc_dispatch_logging_client(bip, (struct link_client *)l,
line);
2005-04-28 10:26:44 +02:00
break;
2007-12-14 22:06:20 +01:00
#ifdef HAVE_LIBSSL
case IRC_TYPE_TRUST_CLIENT:
return irc_dispatch_trust_client((struct link_client *)l, line);
break;
#endif
2005-04-28 10:26:44 +02:00
default:
fatal("irc_dispatch: unknown IRC_TYPE_SERVER");
2005-04-28 10:26:44 +02:00
}
return ERR_PROTOCOL; /* never reached */
}
static int origin_is_me(struct line *l, struct link_server *server)
{
char *nick;
2005-04-28 10:26:44 +02:00
if (!l->origin)
return 0;
nick = nick_from_ircmask(l->origin);
2022-01-09 20:39:38 +01:00
if (!nick)
return 0;
2007-01-26 19:52:21 +01:00
if (strcasecmp(nick, server->nick) == 0) {
2005-04-28 10:26:44 +02:00
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;
2005-04-28 10:26:44 +02:00
struct channel *channel;
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
s_chan = irc_line_elem(line, 1);
2005-04-28 10:26:44 +02:00
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;
2005-04-28 10:26:44 +02:00
2009-01-05 22:57:27 +01:00
s_nick = nick_from_ircmask(line->origin);
2022-01-09 20:39:38 +01:00
// should not happen
if (!s_nick)
return ERR_PROTOCOL;
hash_insert(&channel->ovmasks, s_nick, 0);
2009-01-05 22:57:27 +01:00
free(s_nick);
2005-04-28 10:26:44 +02:00
return OK_COPY;
}
static int irc_332(struct link_server *server, struct line *line)
{
struct channel *channel;
if (irc_line_count(line) != 4)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
2005-04-28 10:26:44 +02:00
/* 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));
2005-04-28 10:26:44 +02:00
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))
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
2005-04-28 10:26:44 +02:00
/* 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");
}
2005-04-28 10:26:44 +02:00
log_init_topic_time(LINK(server)->log, channel->name, channel->creator,
channel->create_ts);
2005-04-28 10:26:44 +02:00
return OK_COPY;
}
static int irc_353(struct link_server *server, struct line *line)
{
struct channel *channel;
const char *names, *eon;
2005-04-28 10:26:44 +02:00
size_t len;
char *nick;
2005-04-28 10:26:44 +02:00
if (irc_line_count(line) != 5)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 3));
2005-04-28 10:26:44 +02:00
/* 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);
2005-04-28 10:26:44 +02:00
}
/* TODO check that type is one of "=" / "*" / "@" */
channel->type = irc_line_elem(line, 2)[0];
2005-04-28 10:26:44 +02:00
names = irc_line_elem(line, 4);
2005-04-28 10:26:44 +02:00
int index;
2005-04-28 10:26:44 +02:00
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"
2018-11-18 15:10:16 +01:00
while ((index = bip_get_index(server->prefixes, *names))) {
ovmask |= 1 << index;
names++;
2005-04-28 10:26:44 +02:00
}
#pragma GCC diagnostic pop
2005-04-28 10:26:44 +02:00
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;
2005-04-28 10:26:44 +02:00
/* we just ignore names for nicks that are crazy long */
if (len + 2 < NAMESIZE)
hash_insert(&channel->ovmasks, nick, (void *)ovmask);
free(nick);
2005-04-28 10:26:44 +02:00
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)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 2));
2005-04-28 10:26:44 +02:00
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 */
2018-11-18 15:10:16 +01:00
static int irc_368(struct link_server *server, struct line *l)
{
2018-11-18 15:10:16 +01:00
(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;
2007-02-08 23:53:48 +01:00
mylog(LOG_DEBUG,
"RPL_ENDOFBANLIST: "
"Decrementing who count for %p: %d",
link->who_client, link->who_client->who_count);
}
}
return OK_COPY_WHO;
}
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
free(c);
}
static int irc_part(struct link_server *server, struct line *line)
{
char *s_nick;
const char *s_chan;
2005-04-28 10:26:44 +02:00
struct channel *channel;
if (irc_line_count(line) != 2 && irc_line_count(line) != 3)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
s_chan = irc_line_elem(line, 1);
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
hash_remove(&server->channels, s_chan);
channel_free(channel);
return OK_COPY;
}
if (!line->origin)
return ERR_PROTOCOL;
2005-04-28 10:26:44 +02:00
s_nick = nick_from_ircmask(line->origin);
2022-01-09 20:39:38 +01:00
// should not happen
if (!s_nick)
return ERR_PROTOCOL;
2009-01-05 22:52:25 +01:00
if (!hash_includes(&channel->ovmasks, s_nick)) {
free(s_nick);
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
2009-01-05 22:52:25 +01:00
}
hash_remove(&channel->ovmasks, s_nick);
2009-01-05 22:52:25 +01:00
free(s_nick);
2005-04-28 10:26:44 +02:00
log_part(LINK(server)->log, line->origin, s_chan,
irc_line_count(line) == 3 ? irc_line_elem(line, 2) : NULL);
2005-04-28 10:26:44 +02:00
return OK_COPY;
}
static void mode_add_letter_uniq(struct link_server *s, char c)
{
size_t i;
2005-04-28 10:26:44 +02:00
for (i = 0; i < s->user_mode_len; i++) {
if (s->user_mode[i] == c)
return;
}
2008-12-11 11:00:05 +01:00
s->user_mode = bip_realloc(s->user_mode, s->user_mode_len + 1);
2005-04-28 10:26:44 +02:00
s->user_mode[s->user_mode_len++] = c;
}
static void mode_remove_letter(struct link_server *s, char c)
{
size_t i;
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
return;
}
}
}
static void irc_user_mode(struct link_server *server, struct line *line)
{
const char *mode;
2005-04-28 10:26:44 +02:00
int add = 1;
for (mode = irc_line_elem(line, 2); *mode; mode++) {
2005-04-28 10:26:44 +02:00
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"
2005-04-28 10:26:44 +02:00
if (add) {
mode_add_letter_uniq(server, *mode);
} else {
mode_remove_letter(server, *mode);
}
#pragma GCC diagnostic pop
2005-04-28 10:26:44 +02:00
}
}
}
static int irc_mode(struct link_server *server, struct line *line)
{
struct channel *channel;
const char *mode;
2005-04-28 10:26:44 +02:00
int add = 1;
int cur_arg = 0;
array_t *mode_args = NULL;
int ret;
if (!irc_line_includes(line, 2))
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
2005-08-01 13:16:39 +02:00
/* 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);
2005-04-28 10:26:44 +02:00
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]))
2005-08-01 13:16:39 +02:00
return ERR_PROTOCOL;
#pragma GCC diagnostic pop
2005-08-01 13:16:39 +02:00
/* channel mode change */
channel = hash_get(&server->channels, irc_line_elem(line, 1));
2005-08-01 13:16:39 +02:00
/* 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);
2005-08-01 13:16:39 +02:00
/*
2005-04-28 10:26:44 +02:00
* MODE -a+b.. #channel args
* ^ ^
* mode cur_arg
*/
for (mode = irc_line_elem(line, 2); *mode; mode++) {
if (*mode == '-')
2005-04-28 10:26:44 +02:00
add = 0;
else if (*mode == '+')
2005-04-28 10:26:44 +02:00
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;
}
}
2018-11-18 15:10:16 +01:00
} else if ((index = bip_get_index(s->usermodes, *mode))) {
nick = irc_line_elem(line, cur_arg + 3);
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
}
#pragma GCC diagnostic pop
2005-04-28 10:26:44 +02:00
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));
2005-04-28 10:26:44 +02:00
return ts;
}
static int irc_topic(struct link_server *server, struct line *line)
{
struct channel *channel;
const char *topic;
2005-04-28 10:26:44 +02:00
if (irc_line_count(line) != 3)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 1));
2005-04-28 10:26:44 +02:00
/* we can't get topic message for chans we're not on */
if (!channel)
return ERR_PROTOCOL;
2005-04-28 10:26:44 +02:00
if (channel->topic)
free(channel->topic);
topic = irc_line_elem(line, 2);
2005-04-28 10:26:44 +02:00
if (*topic == ':')
topic++;
channel->topic = bip_strdup(topic);
2005-04-28 10:26:44 +02:00
/*
* :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);
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
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)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
channel = hash_get(&server->channels, irc_line_elem(line, 1));
2005-04-28 10:26:44 +02:00
/* 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)))
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if (strcasecmp(irc_line_elem(line, 2), server->nick) == 0) {
/* we get kicked !! */
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
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);
}
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
return OK_COPY;
}
2008-01-01 21:24:04 +01:00
static void irc_privmsg_check_ctcp(struct link_server *server,
struct line *line)
{
if (irc_line_count(line) != 3)
2008-01-01 21:24:04 +01:00
return;
if (!line->origin)
return;
char *nick;
nick = nick_from_ircmask(line->origin);
2022-01-09 20:39:38 +01:00
// should not happen
if (!nick)
return;
if (irc_line_elem_equals(line, 2, "\001VERSION\001")) {
2008-01-01 21:24:04 +01:00
WRITE_LINE2(CONN(server), NULL, "NOTICE", nick,
"\001VERSION bip-" PACKAGE_VERSION "\001");
2008-01-01 21:24:04 +01:00
}
free(nick);
}
2005-04-28 10:26:44 +02:00
static int irc_privmsg(struct link_server *server, struct line *line)
{
if (!irc_line_includes(line, 2))
return ERR_PROTOCOL;
2008-12-26 08:56:03 +01:00
if (LINK(server)->s_state == IRCS_CONNECTED)
log_privmsg(LINK(server)->log, line->origin,
irc_line_elem(line, 1), irc_line_elem(line, 2));
2008-01-01 21:24:04 +01:00
irc_privmsg_check_ctcp(server, line);
2005-04-28 10:26:44 +02:00
return OK_COPY;
}
static int irc_notice(struct link_server *server, struct line *line)
{
2008-12-26 08:56:03 +01:00
if (!irc_line_includes(line, 2))
return ERR_PROTOCOL;
2005-05-17 14:52:12 +02:00
if (LINK(server)->s_state == IRCS_CONNECTED)
log_notice(LINK(server)->log, line->origin,
irc_line_elem(line, 1), irc_line_elem(line, 2));
2005-04-28 10:26:44 +02:00
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;
2007-01-27 21:47:56 +01:00
char *org_nick;
const char *dst_nick;
2005-04-28 10:26:44 +02:00
if (irc_line_count(line) != 2)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if (!line->origin)
return ERR_PROTOCOL;
2007-01-27 21:47:56 +01:00
org_nick = nick_from_ircmask(line->origin);
2022-01-09 20:39:38 +01:00
// should not happen
if (!org_nick)
return ERR_PROTOCOL;
dst_nick = irc_line_elem(line, 1);
2007-01-27 21:47:56 +01:00
2005-04-28 10:26:44 +02:00
for (hash_it_init(&server->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
2005-04-28 10:26:44 +02:00
channel = hash_it_item(&hi);
if (!hash_includes(&channel->ovmasks, org_nick))
2005-04-28 10:26:44 +02:00
continue;
hash_rename_key(&channel->ovmasks, org_nick, dst_nick);
log_nick(LINK(server)->log, org_nick, channel->name, dst_nick);
2005-04-28 10:26:44 +02:00
}
2007-01-27 13:30:12 +01:00
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) {
2007-01-27 13:30:12 +01:00
free(LINK(server)->connect_nick);
LINK(server)->connect_nick = bip_strdup(server->nick);
2007-01-27 13:30:12 +01:00
}
}
2007-01-27 21:47:56 +01:00
free(org_nick);
2005-04-28 10:26:44 +02:00
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)
2005-04-28 10:26:44 +02:00
return ERR_PROTOCOL;
if (!line->origin)
return ERR_PROTOCOL;
2005-04-28 10:26:44 +02:00
s_nick = nick_from_ircmask(line->origin);
2022-01-09 20:39:38 +01:00
// should not happen
if (!s_nick)
return ERR_PROTOCOL;
2005-04-28 10:26:44 +02:00
for (hash_it_init(&server->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
2005-04-28 10:26:44 +02:00
channel = hash_it_item(&hi);
if (!hash_includes(&channel->ovmasks, s_nick))
2005-04-28 10:26:44 +02:00
continue;
hash_remove(&channel->ovmasks, s_nick);
2005-05-30 15:20:17 +02:00
log_quit(LINK(server)->log, line->origin, channel->name,
irc_line_includes(line, 1) ? irc_line_elem(line, 1)
: NULL);
2005-04-28 10:26:44 +02:00
}
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;
}
2005-04-28 10:26:44 +02:00
static void irc_server_startup(struct link_server *ircs)
{
char *nick;
char *username, *realname;
2005-04-28 10:26:44 +02:00
/* lower the token number as freenode hates fast login */
CONN(ircs)->token = 1;
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
nick = ircs->nick;
if (LINK(ircs)->away_nick && LINK(ircs)->l_clientc == 0) {
if (nick)
free(nick);
nick = bip_strdup(LINK(ircs)->away_nick);
2005-04-28 10:26:44 +02:00
}
if ((!LINK(ircs)->follow_nick && !LINK(ircs)->away_nick)
|| nick == NULL) {
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
}
ls_set_nick(ircs, nick);
2005-04-28 10:26:44 +02:00
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;
2005-04-28 10:26:44 +02:00
}
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);
2005-04-28 10:26:44 +02:00
CONN(ircc) = newconn;
2021-10-07 18:04:17 +02:00
TYPE(ircc) = IRC_TYPE_LOGGING_CLIENT;
2005-04-28 10:26:44 +02:00
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))
2005-04-28 10:26:44 +02:00
channel_free(hash_it_item(&hi));
hash_clean(&server->channels);
2005-04-28 10:26:44 +02:00
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)
2005-04-28 10:26:44 +02:00
WRITE_LINE1(CONN(is), NULL, "NICK",
LINK(ic)->away_nick);
2006-09-18 18:06:23 +02:00
if (is && LINK(ic)->no_client_away_msg)
WRITE_LINE1(CONN(is), NULL, "AWAY",
LINK(ic)->no_client_away_msg);
2005-04-28 10:26:44 +02:00
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);
2021-10-07 18:04:17 +02:00
} else if (TYPE(ic) == IRC_TYPE_LOGGING_CLIENT) {
2005-04-28 10:26:44 +02:00
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;
}
2005-04-28 10:26:44 +02:00
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));
2005-04-28 10:26:44 +02:00
2005-08-04 14:40:09 +02:00
LINK(is)->l_server = NULL;
2007-08-19 09:59:57 +02:00
irc_server_free((struct link_server *)is);
2005-04-28 10:26:44 +02:00
} 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;
}
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
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");
2005-04-28 10:26:44 +02:00
}
void irc_server_free(struct link_server *s)
{
2007-10-21 19:35:22 +02:00
if (CONN(s))
connection_free(CONN(s));
2005-04-28 10:26:44 +02:00
if (s->nick)
free(s->nick);
if (s->user_mode)
free(s->user_mode);
2007-10-21 19:35:22 +02:00
int i;
char *ptr;
array_each(&s->chanmodes, i, ptr) free(ptr);
MAYFREE(s->prefixes);
MAYFREE(s->usermodes);
2007-10-21 19:35:22 +02:00
hash_iterator_t hi;
for (hash_it_init(&s->channels, &hi); hash_it_item(&hi);
hash_it_next(&hi)) {
2007-10-21 19:35:22 +02:00
struct channel *chan = hash_it_item(&hi);
channel_free(chan);
}
2008-02-16 11:32:13 +01:00
hash_clean(&s->channels);
2007-10-21 19:35:22 +02:00
2005-04-28 10:26:44 +02:00
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,
2005-08-25 10:17:10 +02:00
#ifdef HAVE_LIBSSL
link->network->ssl, link->network->ciphers,
link->ssl_check_mode, link->user->ssl_check_store,
link->user->ssl_client_certfile,
2005-08-25 10:17:10 +02:00
#else
0, NULL, 0, NULL, NULL,
2005-08-25 10:17:10 +02:00
#endif
(time_t)CONNECT_TIMEOUT);
2009-01-10 12:35:59 +01:00
assert(conn);
if (conn->handle == -1) {
2009-07-05 19:04:51 +02:00
mylog(LOG_INFO, "[%s] Cannot connect.", link->name);
connection_free(conn);
server_next(link);
return NULL;
}
2005-04-28 10:26:44 +02:00
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");
}
2005-04-28 10:26:44 +02:00
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);
2005-04-28 10:26:44 +02:00
if (!s->nick)
return;
if (LINK(s)->prev_nick)
free(LINK(s)->prev_nick);
LINK(s)->prev_nick = bip_strdup(s->nick);
2005-04-28 10:26:44 +02:00
}
2007-02-08 20:12:20 +01:00
#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)
2005-05-12 10:29:27 +02:00
{
mylog(LOG_ERROR, "%d %s", bip->write_oidentd, bip->oidentdpath);
if (!bip->write_oidentd || bip->oidentdpath == NULL) {
return;
}
2005-05-12 10:29:27 +02:00
list_iterator_t it;
FILE *f;
2007-02-08 20:12:20 +01:00
char *bipstart = NULL, *bipend = NULL;
struct stat stats;
char tag_written = 0;
2005-05-12 10:29:27 +02:00
if (stat(bip->oidentdpath, &stats) == -1) {
2008-06-01 11:11:03 +02:00
if (errno == ENOENT && (f = fopen(bip->oidentdpath, "w+"))) {
2007-02-08 20:12:20 +01:00
fchmod(fileno(f), 0644);
} else {
mylog(LOG_WARN, "Can't open/create %s",
bip->oidentdpath);
2007-02-08 20:12:20 +01:00
return;
}
} else {
2008-05-31 12:31:30 +02:00
/* strip previously autogenerated content */
char *content;
f = fopen(bip->oidentdpath, "r+");
2007-02-08 20:12:20 +01:00
if (!f) {
mylog(LOG_WARN, "Can't open/create %s",
bip->oidentdpath);
2007-02-08 20:12:20 +01:00
return;
}
2022-01-09 20:35:50 +01:00
// casting to size_t as stat should never return negative size
content = (char *)bip_malloc((size_t)(stats.st_size + 1));
2007-02-08 20:12:20 +01:00
2022-01-09 20:35:50 +01:00
// 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);
2007-02-08 20:12:20 +01:00
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) {
2007-02-08 23:53:48 +01:00
mylog(LOG_DEBUG, "Can't reset %s size",
bip->oidentdpath);
free(content);
2007-02-08 20:12:20 +01:00
goto clean_oidentd;
}
2022-01-09 20:35:50 +01:00
// data preceeding the tag, bipstart >= content (strstr)
fwrite(content, (size_t)1, (size_t)(bipstart - content),
f);
2007-02-08 20:12:20 +01:00
2022-01-09 20:35:50 +01:00
bipend = strstr(bipstart, BIP_OIDENTD_END);
if (bipend == NULL) {
2007-02-08 20:12:20 +01:00
mylog(LOG_WARN, "No %s mark found in %s",
BIP_OIDENTD_END, bip->oidentdpath);
2022-01-09 20:35:50 +01:00
} else {
/* data following the tag
* ...........BIP_OIDENTD_START...BIP_OIDENTD_END..............
* ^content...^bipstart...........^bipend........^remaining
* data
2022-01-09 20:35:50 +01:00
*/
char *remaining =
bipend + BIP_OIDENTD_END_LENGTH;
off_t remaining_len =
stats.st_size - (bipend - content)
- (off_t)BIP_OIDENTD_END_LENGTH;
2022-01-09 20:35:50 +01:00
if (remaining_len < 0) {
mylog(LOG_ERROR,
"oidentd_dump: error parsing %s",
bip->oidentdpath);
2022-01-09 20:35:50 +01:00
goto clean_oidentd;
}
fwrite(remaining, (size_t)1,
(size_t)remaining_len, f);
2022-01-09 20:35:50 +01:00
}
2007-02-08 20:12:20 +01:00
} else {
/* No previous conf */
if (stats.st_size != 0
&& content[stats.st_size - 1] != '\n')
2007-02-08 20:12:20 +01:00
fprintf(f, "\n");
}
2007-10-20 22:57:09 +02:00
free(content);
2007-02-08 20:12:20 +01:00
}
2005-05-12 10:29:27 +02:00
for (list_it_init(&bip->conn_list, &it); list_it_item(&it);
list_it_next(&it)) {
2005-05-12 10:29:27 +02:00
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)) {
2007-02-08 20:12:20 +01:00
struct link_server *ls;
struct link *l;
2005-05-12 10:29:27 +02:00
2007-02-08 20:12:20 +01:00
if (!tag_written) {
fprintf(f, BIP_OIDENTD_START);
tag_written = 1;
}
ls = (struct link_server *)la;
2007-02-08 20:12:20 +01:00
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);
2005-05-12 10:29:27 +02:00
fprintf(f, "}\n");
}
}
2007-02-08 20:12:20 +01:00
if (tag_written)
fprintf(f, BIP_OIDENTD_END);
clean_oidentd:
2005-05-12 10:29:27 +02:00
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);
}
}
}
}
2007-05-26 12:31:44 +02:00
void bip_init(bip_t *bip)
2005-04-28 10:26:44 +02:00
{
2007-05-26 12:31:44 +02:00
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);
2005-04-28 10:26:44 +02:00
hash_init(&bip->users, HASH_NOCASE);
hash_init(&bip->networks, HASH_NOCASE);
2007-05-26 12:31:44 +02:00
}
2005-04-28 10:26:44 +02:00
2007-05-26 12:31:44 +02:00
/* 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)) {
2007-05-26 12:31:44 +02:00
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);
2007-05-26 12:31:44 +02:00
}
} 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);
2007-05-26 12:31:44 +02:00
} else {
link->recon_timer--;
}
}
}
2005-04-28 10:26:44 +02:00
2007-05-26 12:31:44 +02:00
/* drop lagging connecting client */
for (list_it_init(&bip->connecting_client_list, &li); list_it_item(&li);
list_it_next(&li)) {
2007-05-26 12:31:44 +02:00
struct link_client *ic = list_it_item(&li);
ic->logging_timer++;
if (ic->logging_timer > LOGGING_TIMEOUT) {
2007-09-27 01:08:10 +02:00
if (CONN(ic))
list_remove(&bip->conn_list, CONN(ic));
2007-05-26 12:31:44 +02:00
irc_close((struct link_any *)ic);
list_it_remove(&li);
}
}
2005-04-28 10:26:44 +02:00
2007-05-26 12:31:44 +02:00
/*
* Cleanup lagging or dangling who_count buffers
*/
timeout_clean_who_counts(&bip->link_list);
}
2005-05-24 01:05:11 +02:00
2007-05-26 12:31:44 +02:00
void bip_on_event(bip_t *bip, connection_t *conn)
{
struct link_any *lc = (struct link_any *)conn->user_data;
2005-04-28 10:26:44 +02:00
2007-05-26 12:31:44 +02:00
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);
}
2007-05-26 12:31:44 +02:00
return;
}
2005-05-24 01:05:11 +02:00
2007-05-26 12:31:44 +02:00
/* reached only if socket is not listening */
int err;
list_t *linel = read_lines(conn, &err);
if (err) {
if (TYPE(lc) == IRC_TYPE_SERVER) {
2009-07-05 19:04:51 +02:00
mylog(LOG_ERROR, "[%s] read_lines error, closing...",
link_name(lc));
2007-05-26 12:31:44 +02:00
irc_server_shutdown(LINK(lc)->l_server);
} else {
2009-07-05 19:04:51 +02:00
mylog(LOG_ERROR, "client read_lines error, closing...");
2007-05-26 12:31:44 +02:00
}
goto prot_err;
}
if (!linel)
return;
2007-05-26 12:31:44 +02:00
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;
}
2005-04-28 10:26:44 +02:00
line = irc_line_new_from_string(line_s);
2007-05-26 12:31:44 +02:00
if (!line) {
mylog(LOG_ERROR,
"[%s] Can not parse line. Link type: %d. "
"closing...",
link_name(lc), TYPE(lc));
2007-05-26 12:31:44 +02:00
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));
2007-05-26 12:31:44 +02:00
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)
2007-05-26 12:31:44 +02:00
list_remove(&bip->connecting_client_list, lc);
irc_close(lc);
}
}
2005-04-28 10:26:44 +02:00
2007-05-26 12:31:44 +02:00
/*
* 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);
2007-05-26 12:31:44 +02:00
while (!sighup) {
connection_t *conn;
if (timeleft == 0) {
/*
2007-05-26 12:31:44 +02:00
* Compute timeouts for next reconnections and lagouts
*/
2007-05-26 12:31:44 +02:00
timeleft = 1000;
bip_tick(bip);
2005-04-28 10:26:44 +02:00
}
2005-05-12 10:29:27 +02:00
int nc;
2005-04-28 10:26:44 +02:00
/* Da main loop */
2007-05-26 12:31:44 +02:00
list_t *ready = wait_event(&bip->conn_list, &timeleft, &nc);
2005-05-12 10:29:27 +02:00
if (nc)
oidentd_dump(bip);
while ((conn = list_remove_first(ready)))
2007-05-26 12:31:44 +02:00
bip_on_event(bip, conn);
2005-04-28 10:26:44 +02:00
list_free(ready);
}
2007-05-26 12:31:44 +02:00
while (list_remove_first(&bip->connecting_client_list))
2005-05-26 17:36:15 +02:00
;
2005-04-28 10:26:44 +02:00
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)
2005-04-28 10:26:44 +02:00
{
struct link *link;
link = bip_calloc(sizeof(struct link), (size_t)1);
2005-04-28 10:26:44 +02:00
link->l_server = NULL;
2005-04-28 10:26:44 +02:00
hash_init(&link->chan_infos, HASH_NOCASE);
2005-05-30 20:35:49 +02:00
list_init(&link->chan_infos_order, list_ptr_cmp);
2006-09-18 18:06:23 +02:00
list_init(&link->on_connect_send, list_ptr_cmp);
link->autojoin_on_kick = 1;
2009-02-08 12:36:49 +01:00
link->ignore_server_capab = 1;
2005-04-28 10:26:44 +02:00
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);
}
2007-10-21 19:35:22 +02:00
while (link->l_clientc) {
struct link_client *lc = link->l_clientv[0];
if (lc == bip->reloading_client)
bip->reloading_client = NULL;
2007-10-21 19:35:22 +02:00
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
2007-10-21 19:35:22 +02:00
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));
2015-09-10 18:47:53 +02:00
memcpy(s->usermodes, modes + 1, len);
s->usermodes[len] = 0;
2015-09-10 18:47:53 +02:00
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;
}