sortix-mirror/irc/irc.c

1008 lines
26 KiB
C

/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2022 Juhani 'nortti' Krekelä.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* irc.c
* IRC client.
*/
#include <sys/socket.h>
#include <sys/utsname.h>
#include <assert.h>
#if defined(__sortix__)
#include <brand.h>
#endif
#include <err.h>
#include <locale.h>
#include <locale.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <pwd.h>
#include "compat.h"
#include "connection.h"
#include "database.h"
#include "network.h"
#include "scrollback.h"
#include "string.h"
#include "ui.h"
static const char* fix_where(const char* where, bool* op, bool* voice)
{
while ( where[0] == '@' || where[0] == '+' )
{
if ( where[0] == '+' )
{
*op = true;
*voice = true;
}
if ( where[0] == '@' )
{
*op = true;
}
where++;
}
return where;
}
// This should not happen unless the database was faulty or the network is
// faulty (perhaps malicious).
static void database_prediction_mistake(struct network* state, int line)
{
irc_error_linef("database_prediction_mistake() at %s:%i!", __FILE__, line);
(void) state;
}
#define database_prediction_mistake(x) database_prediction_mistake(x, __LINE__)
static void garbage_collect_people(struct network* state)
{
again:
for ( struct person* person = state->people; person; person = person->next_person )
{
if ( !person->channels && !person->always_observable )
{
remove_person(state, person);
goto again; // TODO: This runs in squared time.
}
}
}
void on_startup(struct network* state)
{
(void) state;
}
void on_shutdown(struct network* state)
{
(void) state;
}
void on_nick(struct network* state, const char* who, const char* whomask,
const char* newnick)
{
(void) whomask;
if ( !strnickcmp(who, newnick) )
return; // We don't care if nothing changed.
struct person* person;
// There shouldn't be anyone by the name newnick, let's ensure that.
if ( (person = find_person(state, newnick)) )
{
database_prediction_mistake(state);
// There, unexpectedly, is such a person that doesn't exist and it's me.
if ( !strnickcmp(newnick, state->nick) )
return irc_command_quit_malfunction(state->irc_connection, "network nonsense");
// There isn't a newnick person, but we thought there was, let's forget.
remove_person(state, person);
}
// Locate the person in the database to update the nick.
if ( (person = find_person(state, who)) )
{
char* newnick_copy = strdup(newnick);
if ( !newnick_copy )
return irc_command_quit_malfunction(state->irc_connection, "strdup failure");
free(person->nick);
person->nick = newnick_copy;
for ( struct channel_person* cp = person->channels;
cp;
cp = cp->next_channel_for_person )
{
struct scrollback* sb = get_scrollback(state, cp->channel->name);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*",
"%s is now known as %s", who, newnick);
}
struct scrollback* sb = find_scrollback(state, who);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*",
"%s is now known as %s", who, newnick);
}
// Evidently there was someone with the old nick we didn't know about, but
// now there isn't, and there is someone with the new nick.
else
{
// But we don't actually care about that person, we don't know any
// channels in which that person resides, so that person would get
// garbage collected. This shouldn't happen. The next time that person
// does anything in a channel, we'll put that person in that channel.
database_prediction_mistake(state);
}
// In case I changed my name, update it.
if ( !strnickcmp(who, state->nick) )
{
char* newnick_copy = strdup(newnick);
if ( !newnick_copy )
return irc_command_quit_malfunction(state->irc_connection, "strdup failure");
free(state->nick);
state->nick = newnick_copy;
}
}
void on_quit(struct network* state, const char* who, const char* whomask,
const char* reason)
{
(void) whomask;
(void) reason;
if ( !strnickcmp(who, state->nick) )
return; // We don't care about our own quit message.
// Delete that person from our user database.
struct person* person;
if ( (person = find_person(state, who)) )
{
for ( struct channel_person* cp = person->channels;
cp;
cp = cp->next_channel_for_person )
{
struct scrollback* sb = get_scrollback(state, cp->channel->name);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*", "%s has quit (%s)",
who, reason);
}
struct scrollback* sb = find_scrollback(state, who);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*", "%s has quit (%s)",
who, reason);
remove_person(state, person);
}
else
{
// Oddly, we didn't know about that person. But that's okay, the person
// is now gone, and not knowing about that person is correct.
database_prediction_mistake(state);
}
}
static
void on_as_if_join(struct network* state, const char* who, const char* where)
{
// Check if I'm joining a channel.
if ( !strnickcmp(who, state->nick) )
{
// In case we already thought we were in that channel.
if ( find_channel(state, where) )
{
// I'm already in that channel according to the database.
database_prediction_mistake(state);
assert(find_person_in_channel(state, state->nick, where));
return;
}
struct channel* channel;
if ( !(channel = add_channel(state, where)) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
return;
}
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
database_prediction_mistake(state);
if ( !(channel = add_channel(state, where)) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
// I must be in that channel.
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
}
// Find the person.
struct person* person = get_person(state, who);
if ( !person )
return irc_command_quit_malfunction(state->irc_connection, "get_person failure");
// Check if the person is already in that channel.
if ( find_person_in_channel(state, who, channel->name) )
{
// We mistakenly already thought that person was in this channel.
database_prediction_mistake(state);
return;
}
// Put the person in the channel.
if ( !add_person_to_channel(state, person, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel");
}
void on_join(struct network* state, const char* who, const char* whomask,
const char* where)
{
(void) whomask;
where = fix_where(where, NULL, NULL);
if ( where[0] != '#' )
return; // Join on non-channel doesn't make sense.
on_as_if_join(state, who, where);
struct scrollback* sb = get_scrollback(state, where);
if ( sb )
{
int activity = ACTIVITY_NONTALK;
if ( !strnickcmp(who, state->nick) )
activity = ACTIVITY_NONE;
scrollback_printf(sb, activity, "*", "%s (%s) has joined %s",
who, whomask, where);
}
}
static
void on_as_if_part(struct network* state, const char* who, const char* where)
{
where = fix_where(where, NULL, NULL);
// Check if I'm parting a channel.
if ( !strnickcmp(who, state->nick) )
{
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
// I'm parting a channel I didn't know I was in. Do nothing, as now
// my wrong belief became right.
database_prediction_mistake(state);
return;
}
// Forget about the channel as I'm no longer there to observe it.
remove_channel(state, channel);
// Some people may no longer be observable.
garbage_collect_people(state);
return;
}
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
// Someone parted a channel I didn't know I was in.
database_prediction_mistake(state);
if ( !add_channel(state, where) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
// I must be in that channel.
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
}
// Find the person.
struct person* person = find_person(state, who);
if ( !person )
{
// I didn't know about the person that just left a channel I'm in. But
// since we don't know about that person in any other channel, we'll
// garbage collect them here.
database_prediction_mistake(state);
return;
}
struct channel_person* channel_person = find_person_in_channel(state, who, where);
if ( !channel_person )
{
// I didn't think that person was in this channel. But that's true now,
// so I don't need to do anything.
database_prediction_mistake(state);
return;
}
// Remove the person from the channel.
remove_person_from_channel(state, channel_person);
// If the person no longer shares any channels with us, we won't be notified
// of quits or renames, and thus can't track that person's identity, so we
// garbage collect the person until it comes back in side. Some people are
// always obserable, such as ourselves or people that have engaged in a
// private message to us.
if ( !person->channels && !person->always_observable )
remove_person(state, person);
}
void on_part(struct network* state, const char* who, const char* whomask,
const char* where)
{
(void) whomask;
where = fix_where(where, NULL, NULL);
if ( where[0] != '#' )
return; // Part on non-channel doesn't make sense.
on_as_if_part(state, who, where);
struct scrollback* sb = get_scrollback(state, where);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*", "%s (%s) has left %s",
who, whomask, where);
}
static
void on_evidently_exists(struct network* state, const char* who,
const char* whomask, const char* where)
{
(void) whomask;
if ( where[0] != '#' )
{
// Find the person.
struct person* person = find_person(state, who);
if ( !person )
{
if ( !(person = add_person(state, who)) )
return irc_command_quit_malfunction(state->irc_connection, "get_person failure");
}
// The person has now privately messaged us, so assume this person is
// now always observable.
person->always_observable = true;
return;
}
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
database_prediction_mistake(state);
if ( !add_channel(state, where) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
// I must be in that channel.
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
}
// TODO: The following code makes the assumption that you must be in a
// channel to do particular things and that's a wrong assumption.
return;
// Find the person.
struct person* person = find_person(state, who);
if ( !person )
{
database_prediction_mistake(state);
if ( !(person = add_person(state, who)) )
return irc_command_quit_malfunction(state->irc_connection, "get_person failure");
}
// Check if the person is already in that channel.
if ( !find_person_in_channel(state, who, channel->name) )
{
// We mistakenly already thought that person was in this channel.
database_prediction_mistake(state);
// Put the person in the channel.
if ( !add_person_to_channel(state, person, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel");
}
}
void on_privmsg(struct network* state, const char* who, const char* whomask,
const char* where, const char* what)
{
bool where_op = false;
bool where_voice = false;
where = fix_where(where, &where_op, &where_voice);
on_evidently_exists(state, who, whomask, where);
// TODO: Does this happen? How about if we message ourself? Do we want this
// in an IRC client?
if ( !strnickcmp(who, state->nick) )
return; // We don't care about our own messages.
if ( !strcmp(what, "\x01VERSION\x01") )
{
struct utsname un;
uname(&un);
#if defined(__sortix__)
irc_command_noticef(state->irc_connection, who,
"VERSION %s irc %s %s",
BRAND_DISTRIBUTION_NAME, VERSIONSTR,
BRAND_RELEASE_TAGLINE);
#else
irc_command_noticef(state->irc_connection, who,
"VERSION Sortix irc %s on %s %s",
VERSIONSTR, un.sysname, un.release);
#endif
}
const char* sbname = where[0] == '#' ? where : who;
struct scrollback* sb = get_scrollback(state, sbname);
if ( sb )
{
// TODO: \x01ACTION foo\x01 support.
// TODO: Highlights and such.
scrollback_print(sb, ACTIVITY_TALK, who, what);
}
}
void on_notice(struct network* state, const char* who, const char* whomask,
const char* where, const char* what)
{
bool where_op = false;
bool where_voice = false;
where = fix_where(where, &where_op, &where_voice);
on_evidently_exists(state, who, whomask, where);
if ( !strnickcmp(who, state->nick) )
return; // We don't care about our own messages.
const char* sbname = where[0] == '#' ? where : who;
struct scrollback* sb = get_scrollback(state, sbname);
if ( sb )
{
// TODO: \x01ACTION foo\x01 support.
// TODO: Highlights and such.
// TODO: Print as -who-.
scrollback_print(sb, ACTIVITY_TALK, who, what);
}
}
void on_topic(struct network* state, const char* who, const char* whomask,
const char* where, const char* topic)
{
where = fix_where(where, NULL, NULL);
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
database_prediction_mistake(state);
if ( !(channel = add_channel(state, where)) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
// I must be in that channel.
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
}
(void) who;
(void) whomask;
free(channel->topic);
channel->topic = strdup(topic);
struct scrollback* sb = get_scrollback(state, where);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*",
"%s has changed the topic to: %s", who, topic);
}
void on_kick(struct network* state, const char* who, const char* whomask,
const char* where, const char* target, const char* reason)
{
where = fix_where(where, NULL, NULL);
on_evidently_exists(state, who, whomask, where);
on_as_if_part(state, target, where);
struct scrollback* sb = get_scrollback(state, where);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*", "%s has kicked %s (%s)",
who, target, reason);
}
void on_mode(struct network* state, const char* who, const char* whomask,
const char* where, const char* mode, const char* target)
{
(void) whomask;
where = fix_where(where, NULL, NULL);
on_evidently_exists(state, who, whomask, where);
struct channel_person* cp = find_person_in_channel(state, target, where);
if ( cp )
{
bool set = true;
for ( size_t i = 0; mode[i]; i++ )
{
switch ( mode[i] )
{
case '-': set = false; break;
case '+': set = true; break;
case 'o': cp->is_operator = set; break;
case 'v': cp->is_voiced = set; break;
}
}
}
struct scrollback* sb = get_scrollback(state, where);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONTALK, "*", "%s sets mode %s on %s",
who, mode, target);
}
void on_332(struct network* state, const char* where, const char* topic)
{
where = fix_where(where, NULL, NULL);
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
database_prediction_mistake(state);
if ( !(channel = add_channel(state, where)) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
// I must be in that channel.
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
}
free(channel->topic);
channel->topic = strdup(topic);
struct scrollback* sb = get_scrollback(state, where);
if ( sb )
scrollback_printf(sb, ACTIVITY_NONE, "*", "Topic for %s is: %s",
where, topic);
}
void on_353(struct network* state, const char* wheretype, const char* where,
const char* list)
{
(void) wheretype;
where = fix_where(where, NULL, NULL);
// Find the channel.
struct channel* channel;
if ( !(channel = find_channel(state, where)) )
{
database_prediction_mistake(state);
if ( !add_channel(state, where) )
return irc_command_quit_malfunction(state->irc_connection, "add_channel failure");
// I must be in that channel.
struct person* self = find_person(state, state->nick);
assert(self);
if ( !add_person_to_channel(state, self, channel) )
return irc_command_quit_malfunction(state->irc_connection, "add_person_to_channel failure");
}
char names[512];
strlcpy(names, list, sizeof(names));
char* names_input = names;
char* names_next = NULL;
char* name;
while ( (name = strtok_r(names_input, " ", &names_next)) )
{
bool is_operator = false;
bool is_voiced = false;
if ( name[0] == '@' )
name++, is_operator = true;
else if ( name[0] == '+' )
name++, is_voiced = true;
struct channel_person* channel_person =
get_person_in_channel(state, get_person(state, name), channel);
channel_person->is_operator = is_operator;
channel_person->is_voiced = is_voiced;
names_input = NULL;
}
}
static bool handle_message(struct network* state, const char* orig_message)
{
char message[512];
memcpy(message, orig_message, sizeof(message));
char* parameters[16];
size_t num_parameters;
irc_parse_message_parameter(message, parameters, &num_parameters);
if ( 2 <= num_parameters && !strcmp(parameters[0], "PING") )
{
irc_transmit_format(state->irc_connection, "PONG :%s", parameters[1]);
return true;
}
if ( num_parameters != 1 )
return false;
irc_parse_message_parameter(parameters[0], parameters, &num_parameters);
if ( num_parameters < 1 )
return false;
if ( !strcmp(parameters[1], "332") )
{
if ( num_parameters < 5 )
return false;
const char* where = parameters[3];
const char* topic = parameters[4];
on_332(state, where, topic);
return true;
}
if ( !strcmp(parameters[1], "333") )
{
// TODO: Topic set by.
return true;
}
if ( !strcmp(parameters[1], "353") )
{
if ( num_parameters < 6 )
return false;
const char* wheretype = parameters[3];
const char* where = parameters[4];
const char* list = parameters[5];
on_353(state, wheretype, where, list);
return true;
}
if ( !strcmp(parameters[1], "366") )
{
// TODO: End of /NAMES list.
return true;
}
const char* who;
const char* whomask;
irc_parse_who(parameters[0], &who, &whomask);
if ( num_parameters < 2 )
return false;
if ( num_parameters < 3 )
return false;
if ( !strcmp(parameters[1], "NICK") )
{
const char* new_nick = parameters[2];
on_nick(state, who, whomask, new_nick);
return true;
}
if ( !strcmp(parameters[1], "QUIT") )
{
const char* reason = parameters[2];
on_quit(state, who, whomask, reason);
return true;
}
const char* where = parameters[2];
if ( !strnickcmp(where, state->nick) )
where = who;
if ( !strcmp(parameters[1], "JOIN") )
{
on_join(state, who, whomask, where);
return true;
}
if ( !strcmp(parameters[1], "PART") )
{
on_part(state, who, whomask, where);
return true;
}
if ( num_parameters < 4 )
return false;
if ( !strcmp(parameters[1], "PRIVMSG") )
{
if ( strchr(who, '.') )
return false; // Network message.
const char* what = parameters[3];
on_privmsg(state, who, whomask, where, what);
return true;
}
if ( !strcmp(parameters[1], "NOTICE") )
{
if ( strchr(who, '.') )
return false; // Network message.
const char* what = parameters[3];
on_notice(state, who, whomask, where, what);
return true;
}
if ( !strcmp(parameters[1], "TOPIC") )
{
const char* topic = parameters[3];
on_topic(state, who, whomask, where, topic);
return true;
}
if ( num_parameters < 5 )
return false;
if ( !strcmp(parameters[1], "KICK") )
{
const char* target = parameters[3];
const char* reason = parameters[4];
on_kick(state, who, whomask, where, target, reason);
return true;
}
if ( !strcmp(parameters[1], "MODE") )
{
const char* mode = parameters[3];
const char* target = parameters[4];
on_mode(state, who, whomask, where, mode, target);
return true;
}
return false;
}
static void on_message(struct network* state, const char* message)
{
if ( !handle_message(state, message) )
{
struct scrollback* sb = find_scrollback_network(state);
scrollback_print(sb, ACTIVITY_NONTALK, state->server_hostname, message);
}
}
static void mainloop(struct network* state)
{
struct ui ui;
ui_initialize(&ui, state);
if ( state->password )
{
irc_command_pass(state->irc_connection, state->password);
explicit_bzero(state->password, strlen(state->password));
free(state->password);
}
irc_command_nick(state->irc_connection, state->nick);
irc_command_user(state->irc_connection, state->nick, "localhost",
state->server_hostname, state->real_name);
struct person* self = add_person(state, state->nick);
if ( !self )
{
irc_command_quit_malfunction(state->irc_connection, "add_person failure");
return;
}
self->always_observable = true;
if ( state->autojoin )
{
irc_command_join(state->irc_connection, state->autojoin);
struct scrollback* sb = get_scrollback(state, state->autojoin);
if ( sb )
ui.current = sb;
}
on_startup(state);
while ( true )
{
ui_render(&ui);
if ( state->irc_connection->connectivity_error )
{
irc_error_linef("Exiting main loop due to transmit error");
break;
}
struct pollfd pfds[2];
memset(pfds, 0, sizeof(pfds));
pfds[0].fd = 0;
pfds[0].events = POLLIN;
pfds[1].fd = state->irc_connection->fd;
pfds[1].events = POLLIN;
int status = poll(pfds, 2, -1);
if ( status < 0 )
err(1, "poll");
if ( pfds[0].revents & POLLIN )
{
char buffer[512];
ssize_t amount = read(0, buffer, sizeof(buffer));
if ( amount < 0 )
err(1, "read: stdin");
for ( ssize_t i = 0; i < amount; i++ )
ui_input_char(&ui, buffer[i]);
}
if ( pfds[1].revents & POLLIN )
{
irc_receive_more_bytes(state->irc_connection);
char message[512];
struct timespec now; // TODO: Use this?
while ( irc_receive_message(state->irc_connection, message, &now) )
on_message(state, message);
}
}
on_shutdown(state);
irc_command_quit(state->irc_connection, NULL);
ui_destroy(&ui);
}
int main(int argc, char* argv[])
{
setlocale(LC_ALL, "");
const char* host = NULL;
const char* nick = NULL;
const char* real_name = NULL;
const char* service = "6667";
const char* password = NULL;
const char* autojoin = NULL;
int c;
while ( 0 <= (c = getopt(argc, argv, "h:j:n:N:p:P:")) )
{
switch ( c )
{
case 'h': host = optarg; break;
case 'j': autojoin = optarg; break;
case 'n': nick = optarg; break;
case 'N': real_name = optarg; break;
case 'p': service = optarg; break;
case 'P': password = optarg; break;
default: errx(1, "invalid option -- '%c'", optopt);
}
}
if ( !nick )
{
struct passwd* pwd = getpwuid(getuid());
if ( !pwd )
errx(1, "no -n nick option was passed");
nick = pwd->pw_name;
if ( !real_name )
{
// TODO: How should gecos be properly parsed?
size_t commapos = strcspn(pwd->pw_gecos, ",");
pwd->pw_gecos[commapos] = '\0';
if ( pwd->pw_gecos[0] )
real_name = pwd->pw_gecos;
}
}
if ( !real_name )
real_name = nick;
if ( !host )
errx(1, "no -h host option was passed");
if ( !service )
errx(1, "no -p port/service option was passed");
struct network state;
memset(&state, 0, sizeof(state));
state.nick = strdup(nick);
if ( !state.nick )
err(1, "strdup");
state.real_name = strdup(real_name);
if ( !state.real_name )
err(1, "strdup");
if ( password )
{
state.password = strdup(password);
if ( !state.password )
err(1, "strdup");
explicit_bzero((char*) password, strlen(password));
}
state.server_hostname = strdup(host);
if ( !state.server_hostname )
err(1, "strdup");
state.autojoin = autojoin;
struct addrinfo addrinfo_hints;
memset(&addrinfo_hints, 0, sizeof(addrinfo_hints));
addrinfo_hints.ai_flags = 0;
addrinfo_hints.ai_family = AF_UNSPEC;
addrinfo_hints.ai_socktype = SOCK_STREAM;
addrinfo_hints.ai_protocol = 0;
struct addrinfo* addrinfo;
int ret;
if ( (ret = getaddrinfo(host, service, &addrinfo_hints, &addrinfo)) != 0 )
errx(1, "could not resolve: %s: %s: %s",
host, service, gai_strerror(ret));
int fd = -1;
for ( struct addrinfo* info = addrinfo; info; info = info->ai_next )
{
if ( (fd = socket(info->ai_family, info->ai_socktype | SOCK_CLOEXEC,
info->ai_protocol)) < 0 )
{
warn("socket");
continue;
}
if ( connect(fd, info->ai_addr, info->ai_addrlen) < 0 )
{
warn("connect");
close(fd);
continue;
}
break;
}
if ( fd < 0 )
errx(1, "unable to connect, exiting.");
freeaddrinfo(addrinfo);
struct irc_connection irc_connection;
memset(&irc_connection, 0, sizeof(irc_connection));
irc_connection.fd = fd;
state.irc_connection = &irc_connection;
if ( !add_scrollback(&state, state.server_hostname) )
err(1, "add_scrollback: %s", state.server_hostname);
mainloop(&state);
close(irc_connection.fd);
return 0;
}