/*
* -------------------------------------------------------
* Copyright (C) 2002-2007 Tommi Saviranta <wnd@iki.fi>
* (C) 2002 Lee Hardy <lee@leeh.co.uk>
* (C) 1998-2002 Sebastian Kienzl <zap@riot.org>
* -------------------------------------------------------
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* #define DEBUG */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* ifdef HAVE_CONFIG_H */
#include "miau.h"
#include "conntype.h"
#include "perm.h"
#include "qlog.h"
#include "irc.h"
#include "commands.h"
#include "error.h"
#include "messages.h"
#include "parser.h"
#include "tools.h"
#include "ascii.h"
#include "onconnect.h"
#include "chanlog.h"
#include "privlog.h"
#include "automode.h"
#include "ignore.h"
#include "common.h"
#include "dcc.h"
#include "etc.h"
#include "log.h"
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_CRYPT_H
#include <crypt.h>
#endif /* ifdef HAVE_CRYPT_H */
#ifdef INBOX
FILE *inbox = NULL;
#endif /* ifdef INBOX */
static int read_newclient(void);
static int check_config(void);
static void fakeconnect(connection_type *newclient);
static void sig_term(int a);
static void connect_timeout(int a);
static void create_listen(void);
static void rehash(int a);
static void run(void);
static void pre_init(void);
static void init(void);
static void read_cfg(void);
static void check_timers(void);
static void miau_welcome(void);
static int proceed_timer(int *timer, const int warn, const int exceed);
static int proceed_timer_safe(int *timer, const int warn, const int exceed,
const int repeat);
static void escape(void);
static void setup_atexit(void);
static void setup_home(char *s);
#ifdef DUMPSTATUS
void dump_status(int a);
#endif /* ifdef DUMPSTATUS */
status_type status;
cfg_type cfg = {
1, /* statelog: write stdout into file */
#ifdef QUICKLOG
30, /* qloglength: 30 minutes */
-1, /* autoqlog: full quicklog */
#ifdef QLOGSTAMP
0, /* timestamp: no timestamp */
#endif /* ifdef QLOGSTAMP */
1, /* flushqlog: flush */
#endif /* ifdef QUICKLOG */
#ifdef DCCBOUNCE
0, /* dccbounce: no */
#endif /* ifdef DCCBOUNCE */
#ifdef AUTOMODE
30, /* automodedelay: 30 seconds */
#endif /* ifdef AUTOMODE */
#ifdef INBOX
1,
#endif /* ifdef INBOX */
0, /* listenport: 0 */
2, /* floodtimer */
5, /* burstsize */
30, /* jointries */
1, /* getnick: disconnected */
60, /* getnickinterval: 60 seconds */
0, /* antiidle: no */
1, /* nevergiveup: yes, never give up */
0, /* jumprestricted: no */
90, /* stonedtimeout: 90 seconds */
1, /* rejoin: yes */
30, /* connecttimeout: 30 seconds */
10, /* reconnectdelay: 10 seconds */
0, /* leave: no */
2, /* chandiscon: part */
9, /* maxnicklen: 9 chars */
3, /* maxclients: 3 clients */
1, /* usequitmsg: yes */
1, /* autoaway: detach */
#ifdef PRIVLOG
0, /* privlog: no privlog */
#endif /* ifdef PRIVLOG */
DEFAULT_NICKFILL, /* nickfillchar */
#ifdef NEED_LOGGING
NULL, /* logsuffix */
#endif /* ifdef NEED_LOGGING */
#ifdef DCCBOUNCE
NULL, /* dccbindhost */
#endif /* ifdef DCCBOUNCE */
#ifdef NEED_CMDPASSWD
NULL, /* cmdpasswd */
#endif /* ifdef NEED_CMDPASSWD */
NULL, /* username */
NULL, /* realname */
NULL, /* password */
NULL, /* leavemsg */
NULL, /* bind */
NULL, /* listenhost */
NULL, /* awaymsg */
NULL, /* forwardmsg */
180, /* forwardtime */
NULL, /* channels */
NULL, /* home */
NULL, /* usermode */
0, /* no_idmsg_capab */
0, /* qlog_no_my_quit */
NULL, /* privmsg_fmt */
0, /* newserv_disconn: none. see enum in miau.h */
};
nicknames_type nicknames;
permlist_type connhostlist;
permlist_type ignorelist;
timer_type timers;
#ifdef AUTOMODE
extern permlist_type automodelist;
extern llist_list *tobeautomode;
#endif /* ifdef AUTOMODE */
extern llist_list active_channels;
extern llist_list passive_channels;
client_info i_newclient;
connection_type c_newclient;
int listensocket = 0; /* listensocket */
char *forwardmsg = NULL;
int forwardmsgsize = 0;
int error_code; /* Used for EXIT-macro. Ugly. */
#ifdef PINGSTAT
int ping_sent = 0;
int ping_got = 0;
#endif /* ifdef PINGSTAT */
/*
* Free some resources.
* Only free those that are _not_ must-haves (except nicknames).
*
* Should be called from escape() and from rehash().
*/
static void
free_resources(void)
{
LLIST_EMPTY(nicknames.nicks.head, &nicknames.nicks);
empty_perm(&ignorelist);
empty_perm(&connhostlist);
#ifdef AUTOMODE
empty_perm(&automodelist);
#endif /* ifdef AUTOMODE */
#ifdef ONCONNECT
LLIST_EMPTY(onconnect_actions.head, &onconnect_actions);
#endif /* ifdef ONCONNECT */
FREE(cfg.username);
FREE(cfg.realname);
FREE(cfg.password);
FREE(cfg.privmsg_fmt);
FREE(cfg.bind);
FREE(cfg.usermode);
FREE(cfg.awaymsg);
FREE(cfg.leavemsg);
FREE(cfg.listenhost);
FREE(cfg.channels);
FREE(cfg.forwardmsg);
FREE(cfg.channels);
#ifdef DCCBOUNCE
FREE(cfg.dccbindhost);
#endif /* ifdef DCCBOUNCE */
#ifdef CHANLOG
chanlog_del_rules();
#endif /* ifdef CHANLOG */
#ifdef QUICKLOG
/* flush all of qlog */
qlog_flush(time(NULL), 0);
#endif /* ifdef QUICKLOG */
FREE(status.awaymsg);
} /* static void free_resources(void) */
/*
* Finish things up and quit miau.
*/
static void
escape(void)
{
int n;
/* Clear send queue. */
irc_clear_queue();
#ifdef PRIVLOG
privlog_close_all();
#endif /* ifdef PRIVLOG */
/* Close connections and free client list. */
rawsock_close(listensocket);
sock_close(&c_server);
sock_close(&c_newclient);
LLIST_WALK_H(c_clients.clients->head, connection_type *);
sock_close(data);
llist_delete(node, c_clients.clients);
xfree(data);
LLIST_WALK_F;
c_clients.connected = 0;
/* Close log-file and remove PID-file. */
#ifdef INBOX
if (inbox != NULL) {
fclose(inbox);
}
#endif /* ifdef INBOX */
unlink(FILE_PID);
/* Free permission lists. */
empty_perm(&connhostlist);
#ifdef AUTOMODE
empty_perm(&automodelist);
#endif /* ifdef AUTOMODE */
/* Free server-info. */
xfree(i_server.realname);
for (n = 0; n < RPL_MYINFO_LEN; n++) {
xfree(i_server.greeting[n]);
}
for (n = 0; n < RPL_ISUPPORT_LEN; n++) {
xfree(i_server.isupport[n]);
}
/* Free configuration parameters. */
xfree(cfg.home);
#ifdef NEED_LOGGING
xfree(cfg.logsuffix);
#endif /* ifdef NEED_LOGGING */
#ifdef NEED_CMDPASSWD
xfree(cfg.cmdpasswd);
#endif /* ifdef NEED_CMDPASSWD */
xfree(forwardmsg);
/* Free linked lists. */
LLIST_WALK_H(servers.servers.head, server_type *);
xfree(data->name);
xfree(data->password);
xfree(data);
llist_delete(node, &servers.servers);
LLIST_WALK_F;
xfree(c_clients.clients);
LLIST_WALK_H(active_channels.head, channel_type *);
channel_rem(data, LIST_ACTIVE);
LLIST_WALK_F;
LLIST_WALK_H(passive_channels.head, channel_type *);
channel_rem(data, LIST_PASSIVE);
LLIST_WALK_F;
/* Free status data. */
xfree(status.nickname);
xfree(status.idhostname);
/* Free client data. */
xfree(i_client.nickname);
xfree(i_client.username);
xfree(i_client.hostname);
xfree(i_newclient.nickname);
xfree(i_newclient.username);
xfree(i_newclient.hostname);
/* We're done. */
error(MIAU_ERREXIT);
} /* static void escape(void) */
/*
* (Re)read configuration file.
*/
static void
read_cfg(void)
{
int ret;
/* Set current server to point the fallback server. */
i_server.current = servers.servers.head;
/* Remove servers - all except the first one. */
LLIST_WALK_H(servers.servers.head->next, server_type *);
xfree(data->name);
xfree(data->password);
xfree(node->data);
llist_delete(node, &servers.servers);
LLIST_WALK_F;
servers.amount = 1;
/* Read configuration file. */
ret = parse_cfg(MIAURC);
if (ret == -1) {
error(MIAU_ERRCFG, cfg.home);
exit(ERR_CODE_CONFIG);
}
#ifdef NEED_LOGGING
/* cfg.logsuffix _must_ be set */
if (cfg.logsuffix == NULL) {
cfg.logsuffix = xstrdup("");
}
#endif /* ifdef NEED_LOGGING */
report(MIAU_READ_RC);
} /* static void read_cfg(void) */
static void
sig_term(int a)
{
server_drop(MIAU_SIGTERM);
error(MIAU_SIGTERM);
exit(EXIT_SUCCESS);
} /* static void sig_term(int a) */
#ifdef DUMPSTATUS
static char *dumpdata;
static int foocount = 0;
static void
dump_add(char *data)
{
int addlen;
int dumplen;
addlen = strlen(data);
dumplen = strlen(dumpdata) + addlen;
dumpdata = (char *) xrealloc(dumpdata, dumplen + 1);
strncat(dumpdata, data, addlen);
dumpdata[dumplen] = '\0';
foocount += addlen;
} /* static void dump_add(char *data) */
static void
dump_dump(void)
{
if (dumpdata[0] != '\0') {
fprintf(stderr, "%s\n", dumpdata);
irc_mnotice(&c_clients, status.nickname, "%s", dumpdata);
dumpdata[0] = '\0';
foocount = 0;
}
} /* static void dump_dump(void) */
static void
dump_finish(void)
{
if (dumpdata[0] != '\0') {
dump_dump();
}
} /* static void dump_finish(void) */
static void
dump_status_int(const char *id, const int val)
{
char buf[IRC_MSGLEN];
snprintf(buf, IRC_MSGLEN, " %s=%d", id, val);
buf[IRC_MSGLEN - 1] = '\0';
if (foocount + strlen(buf) > 80) {
dump_dump();
}
dump_add(buf);
} /* static void dump_status_int(const char *id, conat int val) */
static void
dump_status_char(const char *id, const char *val)
{
char buf[IRC_MSGLEN];
snprintf(buf, IRC_MSGLEN, " %s='%s'", id, val != NULL ?
val : "(nothing)");
buf[IRC_MSGLEN - 1] = '\0';
if (foocount + strlen(buf) > 80) {
dump_dump();
}
dump_add(buf);
} /* static void dump_status_char(const char *id, const char *val) */
static void
dump_string(const char *data)
{
fprintf(stderr, "%s\n", data);
irc_mnotice(&c_clients, status.nickname, "%s", data);
} /* static void dump_string(const char *data) */
void
dump_status(int a)
{
dumpdata = (char *) xmalloc(1);
dumpdata[0] = '\0';
#ifdef QUICKLOG
/* First check qlog. */
qlog_check(cfg.qloglength * 60);
#endif /* ifdef QUICKLOG */
dump_string("-- miau status --");
dump_string("config:");
dump_add(" nicknames = {");
LLIST_WALK_H(nicknames.nicks.head, char *);
dump_status_char("nick", data);
LLIST_WALK_F;
dump_add(" }");
dump_dump();
dump_status_char("realname", cfg.realname);
dump_status_char("username", cfg.username);
dump_status_int("listenport", cfg.listenport);
dump_status_char("listenhost", cfg.listenhost);
dump_status_char("bind", cfg.bind);
#ifdef QUICKLOG
dump_status_int("qloglength", cfg.qloglength);
#ifdef QLOGSTAMP
dump_status_int("timestamp", cfg.timestamp);
#endif /* ifdef QLOGSTAMP */
dump_status_int("flushqlog", cfg.flushqlog);
#endif /* ifdef QUICKLOG */
#ifdef DCCBOUNCE
dump_status_int("dccbounce", cfg.dccbounce);
dump_status_char("dccbindhost", cfg.dccbindhost);
#endif /* ifdef DCCBOUNCE */
#ifdef AUTOMODE
dump_status_int("automodedelay", cfg.automodedelay);
#endif /* ifdef AUTOMODE */
#ifdef INBOX
dump_status_int("inbox", cfg.inbox);
#endif /* ifdef INBOX */
dump_status_int("getnick", cfg.getnick);
dump_status_int("getnickinterval", cfg.getnickinterval);
dump_status_int("antiidle", cfg.antiidle);
dump_status_int("nevergiveup", cfg.nevergiveup);
dump_status_int("jumprestricted", cfg.jumprestricted);
dump_status_int("stonedtimeout", cfg.stonedtimeout);
dump_status_int("rejoin", cfg.rejoin);
dump_status_int("connecttimeout", cfg.connecttimeout);
dump_status_int("reconnectdelay", cfg.reconnectdelay);
dump_status_int("leave", cfg.leave);
dump_status_int("chandiscon", cfg.chandiscon);
dump_status_int("maxnicklen", cfg.maxnicklen);
dump_status_int("autoaway", cfg.autoaway);
#ifdef NEED_LOGGING
dump_status_char("logsuffix", cfg.logsuffix);
#endif /* ifdef NEED_LOGGING */
dump_status_char("privmsg_fmt", cfg.privmsg_fmt);
dump_status_int("newserv_disconn", cfg.newserv_disconn);
dump_dump();
dump_string("connhosts:");
dump_string(perm_dump(&connhostlist));
dump_dump();
dump_string("servers:");
LLIST_WALK_H(servers.servers.head, server_type *);
dump_add(" {");
dump_status_char("host", data->name);
dump_status_int("port", data->port);
dump_status_int("working", data->working);
dump_status_char("pass", data->password);
dump_status_int("timeout", data->timeout);
dump_add(" }");
dump_dump();
LLIST_WALK_F;
dump_string("status:");
dump_status_char("nickname", status.nickname);
dump_status_char("idhostname", status.idhostname);
dump_status_int("got_nick", status.got_nick);
dump_status_int("getting_nick", status.getting_nick);
dump_status_int("passok", status.passok);
dump_status_int("init", status.init);
dump_status_int("supress", status.supress);
dump_status_int("allowconnect", status.allowconnect);
dump_status_int("allowreply", status.allowreply);
dump_status_int("reconnectdelay", status.reconnectdelay);
dump_status_int("autojoindone", status.autojoindone);
dump_status_char("away", status.awaymsg);
dump_status_int("awaystate", status.awaystate);
dump_status_int("good_server", status.good_server);
dump_status_int("goodhostname", status.goodhostname);
#ifdef UPTIME
dump_status_int("startup", status.startup);
#endif /* ifdef UPTIME */
dump_status_int("c_clients.connected", c_clients.connected);
dump_dump();
dump_string("active_channels:");
LLIST_WALK_H(active_channels.head, channel_type *);
dump_add(" {");
dump_status_char("name", data->name);
dump_status_int("name_set", data->name_set);
dump_status_char("simple_name", data->simple_name);
dump_status_int("simple_set", data->simple_set);
dump_status_char("key", data->key);
#ifdef QUICKLOG
dump_status_int("hasqlog", data->hasqlog);
#endif /* ifdef QUICKLOG */
#ifdef AUTOMODE
dump_status_int("oper", data->oper);
if (data->mode_queue.head == 0) {
dump_status_int("mode_queue", 0);
} else {
llist_node *ptr;
automode_type *act;
dump_dump();
dump_string(" mode_queue = {");
for (ptr = data->mode_queue.head; ptr != NULL;
ptr = ptr->next) {
act = (automode_type *) ptr->data;
dump_add(" {");
dump_status_char("nick", act->nick);
dump_status_int("mode", (int) act->mode);
dump_add(" }"); dump_dump();
}
dump_string(" }");
}
#endif /* ifdef AUTOMODE */
dump_add(" }"); dump_dump();
LLIST_WALK_F;
dump_dump();
dump_string("passive_channels:");
LLIST_WALK_H(passive_channels.head, channel_type *);
dump_add(" {");
dump_status_char("name", data->name);
dump_status_int("name_set", data->name_set);
dump_status_char("simple_name", data->simple_name);
dump_status_int("simple_set", data->simple_set);
dump_status_char("key", data->key);
dump_status_int("jointries", data->jointries);
#ifdef AUTOMODE
dump_status_int("oper", data->oper);
#endif /* idef AUTOMODE */
#ifdef QUICKLOG
dump_status_int("hasqlog", data->hasqlog);
#endif /* idef QUICKLOG */
dump_add(" }"); dump_dump();
LLIST_WALK_F;
dump_dump();
dump_string("old_channels:");
LLIST_WALK_H(old_channels.head, channel_type *);
dump_add(" {");
dump_status_char("name", data->name);
dump_status_int("name_set", data->name_set);
dump_status_char("simple_name", data->simple_name);
dump_status_int("simple_set", data->simple_set);
dump_status_char("key", data->key);
dump_status_int("jointries", data->jointries);
#ifdef AUTOMODE
dump_status_int("oper", data->oper);
#endif /* idef AUTOMODE */
#ifdef QUICKLOG
dump_status_int("hasqlog", data->hasqlog);
#endif /* idef QUICKLOG */
dump_add(" }"); dump_dump();
LLIST_WALK_F;
dump_dump();
#ifdef PRIVLOG
dump_string("open privlogs:");
LLIST_WALK_H(privlog_get_list()->head, privlog_type *);
dump_add(" {");
dump_status_char("nick", data->nick);
dump_status_int("time", data->updated);
dump_add(" }"); dump_dump();
LLIST_WALK_F;
#endif /* idef PRIVLOG */
#ifdef AUTOMODE
dump_string("automodes:");
LLIST_WALK_H(automodelist.list.head, automode_type *);
dump_add(" {");
dump_status_char("mask", data->nick);
dump_status_int("true", (int) data->mode);
dump_add(" }"); dump_dump();
LLIST_WALK_F;
dump_dump();
#endif /* idef AUTOMODE */
/*
dump_string("forwardmsg:");
dump_string(forwardmsg ? forwardmsg : "-");
*/
dump_finish();
xfree(dumpdata);
} /* void dump_status(int a) */
#endif /* idef DUMPSTATUS */
void
drop_newclient(char *reason)
{
if (i_newclient.connected) {
if (reason) {
sock_setblock(c_server.socket);
irc_write(&c_newclient, MIAU_CLOSINGLINK, reason);
}
sock_close(&c_newclient);
i_newclient.connected = 0;
status.init = 0;
}
} /* void drop_newclient(char *reason) */
/*
* Move timer.
*
* Wrapper for proceed_timer_safe with 0 as last parameter.
*/
static int
proceed_timer(int *timer, const int warn, const int exceed)
{
return proceed_timer_safe(timer, warn, exceed, 0);
} /* static int proceed_timer(int *timer, const int warn, const int exceed) */
/*
* Move timer.
*
* Return 1 if timer > warn, 2 if timer > exceed, otherwise 0.
*/
static int
proceed_timer_safe(int *timer, const int warn, const int exceed,
const int repeat)
{
(*timer)++;
/* Exceed */
if (*timer >= exceed) {
*timer = 0;
return 2;
}
/* Normal warning */
if (*timer == warn) {
return 1;
}
/* Repeated warning */
if (repeat != 0 && *timer > warn && (*timer - warn) % repeat == 0) {
return 1;
}
return 0;
} /* static int proceed_timer_safe(int *timer, const int warn, const int exceed,
const int repeat) */
/*
* Create socket for listening clients.
*
* If old socket exists, close it. If something goes wrong, go down, hard.
*/
static void
create_listen(void)
{
if (listensocket) {
rawsock_close(listensocket);
}
/* Create listener. */
listensocket = sock_open();
if (listensocket == -1) {
error(SOCK_ERROPEN, net_errstr);
exit(ERR_CODE_NETWORK);
}
if (! sock_bind(listensocket, cfg.listenhost, cfg.listenport)) {
if (cfg.listenhost) {
error(SOCK_ERRBINDHOST, cfg.listenhost,
cfg.listenport, net_errstr);
} else {
error(SOCK_ERRBIND, cfg.listenport, net_errstr);
}
exit(ERR_CODE_NETWORK);
}
if (! sock_listen(listensocket)) {
error(SOCK_ERRLISTEN);
exit(ERR_CODE_NETWORK);
}
if (cfg.listenhost) {
report(SOCK_LISTENOKHOST, cfg.listenhost, cfg.listenport);
} else {
report(SOCK_LISTENOK, cfg.listenport);
}
} /* static void create_listen(void) */
/*
* Function noting connect() timeouted. Called thru alert().
*/
static void
connect_timeout(int a)
{
error(SOCK_ERRCONNECT, ((server_type *) i_server.current->data)->name,
SOCK_ERRTIMEOUT);
sock_close(&c_server);
} /* static void connect_timeout(int a) */
static void
create_dirs(void)
{
#ifdef NEED_LOGGING
{
struct stat ds;
int t;
int logdir;
logdir = 0;
#ifdef CHANLOG
if (chanlog_list.head != NULL || global_logtype != 0) {
logdir = 1;
}
#endif /* ifdef CHANLOG */
#ifdef PRIVLOG
if (cfg.privlog != 0) {
logdir = 1;
}
#endif /* ifdef PRIVLOG */
if (logdir == 1) {
t = stat(LOGDIR, &ds);
if (t == 0) {
/* Exists. */
if (! S_ISDIR(ds.st_mode)) {
error(MIAU_ERRLOGDIR, LOGDIR);
exit(ERR_CODE_HOME);
}
} else {
if (mkdir(LOGDIR, 0700) == -1) {
error(MIAU_ERRCREATELOGDIR, LOGDIR);
exit(ERR_CODE_HOME);
}
}
}
}
#endif /* ifdef NEED_LOGGING */
}
/*
* Reread configuration file.
*
* Clear lists but don't touch settings aleady set. Perhaps this isn't a good
* idea and we should reset all values to default...
*
* Parameter for signal handler, value ignored.
*/
static void
rehash(int sigparam)
{
/*
* Save old configuration so we can complain about missing entris and
* detect changes (like changing realname) that require further actions.
*
* We don't need to backup current nick, it's in status.nickname.
*/
char *oldrealname;
char *oldusername;
char *oldpassword;
char *oldbind;
int oldlistenport;
char *oldlistenhost;
llist_node *node;
int host_changed;
int bind_changed;
/* First backup some essential stuff. */
oldrealname = xstrdup(cfg.realname);
oldusername = xstrdup(cfg.username);
oldpassword = xstrdup(cfg.password);
oldlistenport = cfg.listenport;
oldlistenhost = (cfg.listenhost != NULL)
? xstrdup(cfg.listenhost) : NULL;
oldbind = (cfg.bind != NULL) ? xstrdup(cfg.bind) : NULL;
/* Free non-must parameters. */
free_resources();
cfg.listenport = 0;
#ifdef CHANLOG
/* Close open logfiles. */
LLIST_WALK_H(active_channels.head, channel_type *);
chanlog_close(data);
LLIST_WALK_F;
global_logtype = 0; /* No logging by default. */
#endif /* idef CHANLOG */
/* Re-read miaurc and check results. */
read_cfg();
if (check_config() != 0) {
irc_mwrite(&c_clients, ":miau NOTICE %s :%s",
status.nickname,
CLNT_MIAURCBEENWARNED);
}
/*
* realname, username, password and listenport are required and we want
* to revert then back if they're not configured. We need to duplicate
* them as oldvalues will be freed later.
*/
if (cfg.realname == NULL) { cfg.realname = xstrdup(oldrealname); }
if (cfg.username == NULL) { cfg.username = xstrdup(oldusername); }
if (cfg.password == NULL) { cfg.password = xstrdup(oldpassword); }
if (cfg.listenport == 0) { cfg.listenport = oldlistenport; }
#ifdef CHANLOG
/* Open logs. */
LLIST_WALK_H(active_channels.head, channel_type *);
chanlog_open(data);
LLIST_WALK_F;
#endif /* idef CHANLOG */
#ifdef QUICKLOG
/*
* Empty quicklog if flushqlog is set to true and we have clients
* connected or if qloglength is set to 0.
*/
if ((cfg.flushqlog == 1 && c_clients.connected > 0) ||
cfg.qloglength == 0) {
qlog_flush(time(NULL), 0);
}
#endif /* idef QUICKLOG */
/* By default, we don't know anything about server. */
servers.fresh = 1;
/*
* If there was no nicks defined in configuration-file, save current
* nick to the list. Warnings have been send already.
*/
if (nicknames.nicks.head == NULL) {
node = llist_create(xstrdup(status.nickname));
llist_add(node, &nicknames.nicks);
/* We're happy with the nick we have. */
status.got_nick = 1;
if (cfg.nickfillchar == '\0') {
cfg.nickfillchar = DEFAULT_NICKFILL;
}
}
/* Ok, we know we _do_ have a nick. Lets see if it's a good one. */
if (xstrcasecmp((char *) nicknames.nicks.head->data,
status.nickname) != 0) {
status.got_nick = 0;
timers.nickname = cfg.getnickinterval;
}
/* Next nick we try is the first one on the list. */
nicknames.next = NICK_FIRST;
/* Listening port or host changed. */
if (cfg.listenhost == NULL || oldlistenhost == NULL) {
if (cfg.listenhost != oldlistenhost) {
host_changed = 1;
} else {
host_changed = 0;
}
} else {
host_changed = xstrcmp(oldlistenhost, cfg.listenhost);
}
if (oldlistenport != cfg.listenport || host_changed != 0) {
create_listen();
}
/* Bind address changed. */
if (cfg.bind == NULL || oldbind == NULL) {
if (cfg.bind != oldbind) {
bind_changed = 1;
} else {
bind_changed = 0;
}
} else {
bind_changed = xstrcmp(oldbind, cfg.bind);
}
if (bind_changed != 0) {
server_drop(MIAU_RECONNECT);
server_change(1, 0); /* reconnect to current */
}
#ifdef INBOX
/* We're logging no more. */
if (cfg.inbox == 0 && inbox != NULL) {
fclose(inbox);
inbox = NULL;
}
/* We should start logging. */
if (cfg.inbox == 1 && inbox == NULL) {
inbox = fopen(FILE_INBOX, "a+");
if (inbox == NULL) {
report(MIAU_ERRINBOXFILE);
}
}
#endif /* ifdef INBOX */
/* Reopen log-file. */
/*
* Hmm, this doesn't work.
*
if (! freopen(FILE_LOG, "a", stdout)) {
irc_mnotice(&c_clients, status.nickname, MIAU_ERRLOGCONN);
freopen("/dev/null", "a", stdout);
}
*/
/* Real name or user name changed. */
if (xstrcmp(oldrealname, cfg.realname) != 0
|| xstrcmp(oldusername, cfg.username) != 0) {
server_drop(MIAU_RECONNECT);
report(MIAU_RECONNECT);
i_server.current--;
status.reconnectdelay = cfg.reconnectdelay;
timers.connect = cfg.reconnectdelay - 3;
server_change(1, 0);
}
/* Free backuped stuff. */
xfree(oldrealname);
xfree(oldpassword);
xfree(oldusername);
xfree(oldlistenhost);
xfree(oldbind);
create_dirs();
} /* static void rehash(int a) */
/*
* Client disconnected from bouncer.
*
* If appropriate, leave channels and set user away.
*/
/* TODO: Oh man this function and client_drop are a mess together! */
void
clients_left(const char *reason)
{
char *chans; /* Channels we're going to part/leave. */
char *channel;
const char *leavemsg;
const char *awaymsg;
if (reason == NULL) {
awaymsg = cfg.awaymsg;
leavemsg = cfg.leavemsg;
} else {
awaymsg = cfg.usequitmsg ? reason : cfg.awaymsg;
leavemsg = cfg.usequitmsg ? reason : cfg.leavemsg;
}
chans = (char *) xcalloc(1, 1);
/*
* When all clients have detached from miau...
*
* ...if cfg.leave == true, part channels depending on
* configuration and if cfg.rejoin == true, also move channels from
* active_channels to passive_channels.
*
* Because "The PART command causes the user sending the message to be
* removed ... This request is always granted by the server." (RFC 2812)
* we won't bother tracking when we actually have _left_ the channel.
*/
if (active_channels.head != NULL) {
int nlen;
/* Build a string of channel list. */
LLIST_WALK_H(active_channels.head, channel_type *);
/* paranoid */
nlen = strlen(data->name);
chans = (char *) xrealloc(chans, nlen + 2
+ (int) strlen(chans));
strcat(chans, ",");
strncat(chans, data->name, nlen);
if (cfg.leave) {
if (cfg.rejoin) {
/*
* Need to move channel from
* active_channels to passive_channels.
* This means that we have left the
* channels and lost all our
* priviledges.
*/
#ifdef AUTOMODE
data->oper = -1;
#endif /* ifdef AUTOMODE */
llist_add_tail(llist_create(data),
&passive_channels);
} else {
/*
* Not moving channels from list to
* list, therefore freeing
* resources.
*/
channel_free(data);
}
/* Remove channel node from old list. */
llist_delete(node, &active_channels);
}
LLIST_WALK_F;
#ifdef CHANLOG
/* We don't want to leave channels nor message channels. */
if (! cfg.leave && cfg.leavemsg == NULL) {
chanlog_write_entry_all(LOG_MIAU, LOGM_MIAU,
get_short_localtime(), "dis");
}
else
#endif /* ifdef CHANLOG */
/* We want to send an ACTION on each channel. */
if (! cfg.leave && cfg.leavemsg != NULL) {
channel = strtok(chans + 1, ",");
while (channel != NULL) {
irc_write(&c_server,
"PRIVMSG %s :\1ACTION %s\1",
channel, leavemsg);
channel = strtok(NULL, ",");
}
}
/* We want to part each channel with a message. */
else if (cfg.leave == 1 && (cfg.leavemsg != NULL
|| reason != NULL)) {
irc_write(&c_server, "PART %s :%s",
chans + 1, leavemsg);
}
/* Ok, we just want to part channels. */
else if (cfg.leave && cfg.leavemsg == NULL) {
report(MIAU_LEAVING);
irc_write(&c_server, "JOIN 0"); /* RFC 2812 */
}
}
/* Finally, free chans. */
xfree(chans);
if (cfg.autoaway != 0) {
/* Try setting user away with given message. */
set_away(awaymsg);
}
} /* void clients_left(const char *reason) */
/*
* Check timers.
*/
static void
check_timers(void)
{
llist_node *client;
llist_node *client_next;
connection_type *client_con;
static time_t oldtime = 0;
static time_t floodtime = 0;
static time_t newtime;
FILE *fwd;
/* Don't bother doing this too often. */
if (time(&newtime) <= oldtime) {
return;
}
oldtime = newtime;
/*
* If cfg.floodtimer second(s) have elapsed (since last allowance) allow
* sending anohter message. Don't forget to limit the counter to
* cfg.burstsize.
*/
if (newtime - floodtime >= cfg.floodtimer && msgtimer < cfg.burstsize) {
if (cfg.floodtimer != 0) {
msgtimer += (newtime - floodtime) / cfg.floodtimer;
if (msgtimer > cfg.burstsize) {
msgtimer = cfg.burstsize;
}
} else {
/*
* cfg.floodtimer == 0, send cfg.burstsize
* lines at once.
*/
msgtimer = cfg.burstsize;
}
floodtime = newtime;
irc_process_queue();
}
/*
* Countdown to next try to connect the server.
*
* We actually do nothing here - except advance the timer and make
* sure it doesn't reach CONN_DISABLED. Server is tried to connect in
* run().
*/
if (cfg.reconnectdelay != CONN_DISABLED) {
timers.connect++;
}
#ifdef NEED_PROCESS_IGNORES
ignores_process();
#endif /* ifdef NEED_PROCESS_IGNORES */
client = c_clients.clients->head;
while (client != NULL) {
client_next = client->next;
client_con = (connection_type *) client->data;
/* From time to time, see if connected clients are alive. */
switch (proceed_timer_safe(&client_con->timer, 60, 120, 10)) {
case 0:
break;
case 1:
irc_write(client_con, "PING :%s",
i_server.realname);
break;
case 2:
/* (single client, message, error, echo, no) */
client_drop(client_con, CLNT_STONED,
DISCONNECT_ERROR, 1, NULL);
/* error(CLNT_STONED); */
break;
}
client = client_next;
}
if (c_clients.connected == 0) {
/* Client is not connected. Anti-idle. */
if (cfg.antiidle && proceed_timer(&timers.antiidle, 0,
cfg.antiidle * 60) == 2) {
irc_write(&c_server, "PRIVMSG");
}
}
if (i_server.connected > 0) {
int timeout = (i_server.connected == 2) ?
cfg.stonedtimeout : 120;
int warn;
/*
* If we have already registered our nick, timeout is
* configured in miaurc (default 90 seconds). If we are
* still registering our nick, timeout is 120 seconds.
*
* Thanks to netsplits we cannot simply wait until, say, 30
* seconds before timeout, but we'll have to contantly ping the
* server... I hope pinging (at least) every 45 seconds is ok.
*/
warn = (timeout / 2 > 45) ? 45 : timeout / 2;
if (warn < 5) {
warn = 5;
}
switch (proceed_timer_safe(&c_server.timer, warn, timeout, 5)) {
case 0:
/*
* We are connected and we're not lagging -
* not too bad at least. If the server keeps
* serving us this well, try this server again
* if we get disconnected.
*/
if (! status.good_server
&& i_server.connected == 2
&& proceed_timer(
&timers.good_server,
GOOD_SERVER_DELAY,
GOOD_SERVER_DELAY)) {
status.good_server = 1;
}
break;
case 1:
irc_write(&c_server, "PING :%s",
i_server.realname);
#ifdef PINGSTAT
ping_sent++;
#ifdef ENDUSERDEBUG
if (c_clients.connected > 0
&& c_server.timer >
(timeout - 20 >= 5 ?
timeout - 20 : 5)) {
enduserdebug("pinging server (%d/%d)",
c_server.timer,
timeout);
}
#endif /* ifdef ENDUSERDEBUG */
#endif /* ifdef PINGSTAT */
break;
case 2:
server_drop(SERV_STONED);
error(SERV_STONED);
server_change(1, 0);
break;
}
}
if (i_newclient.connected) {
/* Client is connecting... */
switch (proceed_timer(&c_newclient.timer, 0, 30)) {
case 0:
case 1:
break;
case 2:
if (status.init) {
error(CLNT_AUTHFAIL);
irc_write(&c_newclient,
":%s 464 %s :Password Incorrect",
i_server.realname,
(char *) nicknames.nicks.head->data);
drop_newclient("Bad Password");
}
else {
error(CLNT_AUTHTO);
drop_newclient("Ping timeout");
}
if (c_clients.connected > 0) {
irc_mnotice(&c_clients,
status.nickname,
CLNT_AUTHFAILNOTICE,
i_newclient.hostname);
}
/*
* Deallocate would also happen on next
* newclient-connect, but let's do it here.
*/
FREE(i_newclient.nickname);
FREE(i_newclient.username);
FREE(i_newclient.hostname);
break;
}
}
/* Throttle allowed connections. */
if (proceed_timer(&timers.listen, 0, 15) == 2) {
status.allowconnect = 1;
}
/* Throttle allowed replies. */
if (proceed_timer(&timers.reply, 0, 4) == 2) {
status.allowreply = 1;
}
/*
* Try to get nick if message queue is non-full and other factors
* allow it, too.
*/
if (! status.got_nick && cfg.getnick != 0 && i_server.connected == 2
&& msgtimer > 0) {
/* FIXME: don't trust bits of cfg.getnick anymore */
if (proceed_timer(&timers.nickname, 0,
cfg.getnickinterval) == 2) {
if ((c_clients.connected <= 0 && (cfg.getnick & 1)) ||
(c_clients.connected > 0 &&
(cfg.getnick & 2))) {
/* Getting nick now, increase counter. */
status.getting_nick++;
irc_write(&c_server, "NICK %s",
(char *)
nicknames.nicks.head->data);
}
}
}
/* Try to join unjoined channels (when appropriate). */
if (i_server.connected == 2 && cfg.rejoin
&& passive_channels.head != NULL
&& ! (c_clients.connected == 0 && cfg.leave == 1)) {
/*
* Perhaps we don't need to define tryjoininterval.
* Let's just try joining unjoined channels once a minute
* -- unless we're done trying of course.
*/
if (proceed_timer(&timers.join, 0, JOINTRYINTERVAL) == 2) {
channel_join_list(LIST_PASSIVE, 0, NULL);
}
}
/* Handle forwarded messages. */
if (cfg.forwardmsg) {
switch (proceed_timer(&timers.forward, 0, cfg.forwardtime)) {
case 0:
case 1:
break;
case 2:
if (forwardmsg == NULL) {
/* Nothing to forward. */
break;
}
/* yup, popen isn't safe */
fwd = popen(cfg.forwardmsg, "w");
/*
* If there's an error while creating a pipe,
* save messages and try forwarding them
* later. It probably would be nice to limit
* memory allocated by forwarded messages
* somehow... TODO.
*/
if (fwd) {
fprintf(fwd, "%s", forwardmsg);
pclose(fwd);
xfree(forwardmsg);
forwardmsg = NULL;
/* redundant, but makes it clearer */
forwardmsgsize = 0;
}
break;
}
}
#ifdef AUTOMODE
/* Act as op-o-matic. */
if (status.automodes > 0 && proceed_timer(&timers.automode,
0, cfg.automodedelay) == 2) {
/* automode() checks if we have operator-status. */
automode_do();
}
#endif /* ifdef AUTOMODE */
#ifdef PRIVLOG
/* Close logfiles that haven't been used for a while. */
if (privlog_has_open() && proceed_timer(&timers.privlog,
0, PRIVLOG_CHECK_PERIOD)) {
privlog_close_old();
}
#endif /* ifdef PRIVLOG */
#ifdef NEED_LOGGING
/* Reset timer for repeating warnings about logfiles. */
if (proceed_timer(&timers.logfile_warn, 0, LOGWARN_TIMER)) {
log_reset_warn_timer();
}
#endif /* ifdef NEED_LOGGING */
#ifdef DCCBOUNCE
/* Bounce DCCs. */
if (cfg.dccbounce) {
dcc_timer();
}
#endif /* ifdef DCCBOUNCE */
} /* static void check_timers(void) */
void
get_nick(char *format)
{
/*
* Change nick if we're still introducing outself to the server or if
* we were forced to change ...
*/
if (i_server.connected != 2) {
char *badnick;
badnick = xstrdup(status.nickname);
/* if (badnick == NULL) { escape(); } */
/* Try first nick. */
if (nicknames.next == NICK_FIRST) {
nicknames.current = nicknames.nicks.head;
nicknames.next = NICK_NEXT;
nicknames.gen_tries = 0;
}
/* Try next nick on the list. */
else if (nicknames.next == NICK_NEXT) {
nicknames.current = nicknames.current->next;
}
/*
* If we still don't have a nick and we don't want to
* generate one.
*/
if (nicknames.current == NULL && cfg.nickfillchar == '\0') {
nicknames.current = nicknames.nicks.head;
/*
* If nicknames-list happends to be empty, we will -
* we like it or not - generate a nick.
*/
if (nicknames.current->data != NULL) {
xfree(status.nickname);
status.nickname =
xstrdup(nicknames.current->data);
}
}
/*
* else if (nicknames.current == NULL &&
* cfg.nickfillchar != '\0') {
*/
else if (nicknames.current == NULL) {
/* create a nick */
nicknames.next = NICK_GEN;
if (nicknames.gen_tries == 0) {
status.nickname =
(char *) xrealloc(status.nickname,
cfg.maxnicklen + 1);
strncpy(status.nickname,
nicknames.nicks.head->data,
cfg.maxnicklen);
status.nickname[cfg.maxnicklen] = '\0';
}
if (nicknames.gen_tries == cfg.maxnicklen * 2) {
/* No luck rotating. Try all random. */
/* len * 2 is overkill but enough. ;-) */
status.nickname[0] = '\0';
} else {
nicknames.gen_tries++;
}
status.nickname = xrealloc(status.nickname,
cfg.maxnicklen + 1);
randname(status.nickname, cfg.maxnicklen,
cfg.nickfillchar);
} else {
xfree(status.nickname);
status.nickname = (char *) xstrdup((char *)
nicknames.current->data);
}
report(format, badnick, status.nickname);
/* Getting nick, increase sent NICK-command counter. */
status.getting_nick++;
irc_write(&c_server, "NICK %s", status.nickname);
xfree(badnick);
}
status.got_nick = 0;
} /* void get_nick(char *format) */
/*
* User has connected to bouncer.
*/
static void
fakeconnect(connection_type *newclient)
{
int i;
#ifdef ASCIIART
int pic;
#endif /* ifdef ASCIIART */
if (status.nickname == NULL) {
/* Get us a nick if we don't have one already. */
status.nickname = xstrdup((char *) nicknames.nicks.head->data);
}
if (i_server.connected == 2) {
/* Print server information. */
irc_write(newclient, ":%s 001 %s %s %s!~%s@%s",
i_server.realname,
status.nickname,
i_server.greeting[0],
status.nickname,
i_client.username,
i_client.hostname);
/*
* Don't take it for granted that we would get all these...
* Network could be broken you know.
*/
for (i = 1; i < 4; i++) {
if (i_server.greeting[i] != NULL) {
irc_write(newclient,
":%s %03d %s %s",
i_server.realname,
i + 1,
status.nickname,
i_server.greeting[i]);
}
}
for (i = 0; i < 3; i++) {
if (i_server.isupport[i] != NULL) {
irc_write(newclient, ":%s 005 %s %s",
i_server.realname,
status.nickname,
i_server.isupport[i]);
}
}
/* Fetch some info about users. */
irc_write(&c_server, "LUSERS");
} else {
miau_welcome();
}
irc_write(newclient, ":%s 375 %s :"MIAU_VERSION,
i_server.realname,
status.nickname);
#ifdef ASCIIART
/* Print a pretty picture. */
/* Cats sleep when they want to. */
pic = rand() % 2;
for (i = 0; i < PIC_Y; i++) {
irc_write(newclient, ":%s 372 %s :%s",
i_server.realname,
status.nickname,
pics[pic][i]);
}
#endif /* ifdef ASCIIART */
if (i_server.connected == 2) {
irc_write(newclient, ":%s 372 %s :"MIAU_372_RUNNING,
i_server.realname,
status.nickname,
((server_type *) servers.servers.head->data)->name,
status.nickname);
} else {
irc_write(newclient, ":miau 372 %s :"MIAU_372_NOT_CONN,
status.nickname);
}
#ifdef INBOX
#ifdef QUICKLOG
/* Move old qlog-lines to privmsglog. */
qlog_flush(time(NULL) - cfg.qloglength * 60, 1);
#endif /* ifdef QUICKLOG */
if (inbox != NULL && ftell(inbox) != 0) {
irc_write(newclient, ":%s 372 %s :- "CLNT_HAVEMSGS,
i_server.realname,
status.nickname);
} else {
irc_write(newclient, ":%s 372 %s :- "CLNT_INBOXEMPTY,
i_server.realname,
status.nickname);
}
#endif /* ifdef INBOX */
irc_write(newclient, ":%s 376 %s :"MIAU_END_OF_MOTD,
i_server.realname,
status.nickname);
if (i_server.connected == 2) {
/* tell client to change nick. yes, case sensitive. */
if (xstrcmp(i_client.nickname, status.nickname) != 0) {
irc_write(newclient, ":%s NICK :%s",
i_client.nickname,
status.nickname);
xfree(i_client.nickname);
i_client.nickname = xstrdup(status.nickname);
}
/*
* And we were away and didn't have hand-set away-message,
* remove away-status.
*/
if (! (status.awaystate & CUSTOM)
&& (status.awaystate & AWAY)) {
irc_write(&c_server, "AWAY");
status.awaystate &= ~AWAY;
}
}
server_check_list();
/* Tell client if we're supposedly marked as being away. */
if ((status.awaystate & AWAY)) {
irc_write(newclient, ":%s 306 %s :"IRC_AWAY,
i_server.realname,
status.nickname);
}
/*
* Ok, client knows what's going on. If we're connected to server,
* we have things to do, mister.
*/
if (i_server.connected == 2) {
#ifdef QUICKLOG
int qlog;
if (cfg.autoqlog == -1) {
qlog = cfg.qloglength;
} else {
qlog = cfg.autoqlog;
}
if (qlog > 0) {
qlog_check(qlog * 60);
}
#endif /* QUICKLOG */
/*
* First of all, join passive channels.
*
* Passive channels are channels that are either defined
* in miaurc and are not yet joined or channels, that we
* were on when clients detached.
*
* Sending JOIN to server doesn't remove channels from the
* list - positive JOIN-reply from server is needed for that.
*
* TODO: Accept denial from server.
*/
channel_join_list(LIST_PASSIVE, 1, NULL);
#ifdef QUICKLOG
#endif /* ifdef QUICKLOG */
/*
* Next, tell client to join active channels.
*
* Active channels are channels that we currently are on.
*
* This means this list won't get flushed either.
*/
channel_join_list(LIST_ACTIVE, 0, newclient);
#ifdef QUICKLOG
if (qlog > 0) {
/* print headers, replay log, print footer */
qlog_replay_header(newclient);
qlog_replay(newclient, qlog * 60);
qlog_replay_footer(newclient);
if (cfg.flushqlog == 1) {
qlog_flush(time(NULL), 0);
}
}
#endif /* ifdef QUICKLOG */
}
} /* static void fakeconnect(connection_type *newclient) */
/*
* Say hello to bouncer.
*
* Some IRC-clients take server's name from this message - this is why we
* send them this welcome-message when jumping off the server.
*/
static void
miau_welcome(void)
{
irc_mwrite(&c_clients, ":miau 001 %s :%s %s!user@host",
status.nickname,
MIAU_WELCOME,
status.nickname);
} /* static void miau_welcome(void) */
/*
* Set user away if...
*/
void
set_away(const char *msg)
{
/*
* Not connected or no provided away-message and cfg.awaymsg unset?
* Don't change away-message!
* */
if (i_server.connected != 2 || (msg == NULL && cfg.awaymsg == NULL)) {
return;
}
/* Using hand-set away-message? */
if ((status.awaystate & CUSTOM)) {
/* If we're not away, set us away. Otherwise do nothing. */
if (! (status.awaystate & AWAY)) {
irc_write(&c_server, "AWAY :%s", status.awaymsg);
status.awaystate |= AWAY;
}
return;
}
/*
* Ok, we got this far. We know that we're not using custom awaymsg.
*
* If away-message was not provided and we still have one more more
* client connected, do nothing.
*/
if (msg == NULL && c_clients.connected > 0) {
return;
}
/*
* If away-reason was given, use it.
*/
if (msg != NULL) {
xfree(status.awaymsg);
status.awaymsg = xstrdup(msg);
/* Unset us away so we get marked later. */
status.awaystate &= ~AWAY;
}
/* Reached this far being away? Well then, there's nothing to do. */
if ((status.awaystate & AWAY)) {
return;
}
/* Finally, mark us away. */
irc_write(&c_server, "AWAY :%s", (status.awaymsg != NULL)
? status.awaymsg : cfg.awaymsg);
status.awaystate |= AWAY;
} /* void set_away(const char *msg) */
static int
read_newclient(void)
{
llist_node *node;
char *command;
char *param;
int t;
t = irc_read(&c_newclient);
if (t <= 0) {
return t;
}
c_newclient.buffer[30] = 0;
command = strtok(c_newclient.buffer, " ");
param = strtok(NULL, "\0");
if (command == NULL || param == NULL) {
return 0;
}
upcase(command);
if ((xstrcmp(command, "PASS") == 0) && !(status.init & 1)) {
/* Only accept password once */
if (*param == ':') {
param++;
}
if (strlen(cfg.password) == 13) {
/* Assume it's crypted */
char *crypted;
crypted = crypt(param, cfg.password);
if (xstrcmp(crypted, cfg.password) == 0) {
status.passok = 1;
}
} else {
if (xstrcmp(param, cfg.password) == 0) {
status.passok = 1;
}
}
status.init = status.init | 1;
}
if (xstrcmp(command, "NICK") == 0) {
status.init = status.init | 2;
xfree(i_newclient.nickname);
i_newclient.nickname = xstrdup(
strtok(param, " "));
}
if (xstrcmp(command, "USER") == 0) {
status.init = status.init | 4;
xfree(i_newclient.username);
i_newclient.username = xstrdup(
strtok(param, " "));
}
if (status.init == 7 && status.passok == 1) {
connection_type *newclient;
/* Client is in! */
report(CLNT_AUTHOK);
xfree(i_client.nickname);
xfree(i_client.username);
xfree(i_client.hostname);
i_client.nickname = i_newclient.nickname;
i_client.username = i_newclient.username;
i_client.hostname = i_newclient.hostname;
i_newclient.nickname = NULL;
i_newclient.username = NULL;
i_newclient.hostname = NULL;
newclient = (connection_type *)
xmalloc(sizeof(connection_type));
node = llist_create(newclient);
llist_add(node, c_clients.clients);
c_clients.connected++;
newclient->socket = c_newclient.socket;
newclient->timer = 0;
newclient->offset = 0;
i_newclient.connected = 0;
c_newclient.socket = 0;
status.passok = 0;
status.init = 0;
fakeconnect(newclient);
/*
* Reporting number of connected clients
* is irrevelant if only one is allowed at
* a time.
*/
if (cfg.maxclients != 1) {
report(CLNT_CLIENTS, c_clients.connected);
}
}
return 0;
} /* static int read_newclient(void) */
#ifdef QUICKLOG
static time_t
get_time(char *param)
{
char *ptr;
int days, hours, minutes;
if (param == NULL) {
return 0;
}
days = hours = minutes = 0;
ptr = strrchr(param, (int) ':');
if (ptr == NULL) {
minutes = atoi(param);
} else {
minutes = atoi(ptr + 1);
*ptr = '\0';
ptr = strrchr(param, (int) ':');
if (ptr == NULL) {
hours = atoi(param);
} else {
hours = atoi(ptr + 1);
*ptr = '\0';
days = atoi(param);
}
}
return days * 1440 + hours * 60 + minutes;
} /* static time_t get_time(char *param) */
#endif /* ifdef QUICKLOG */
/*
* Handle commands provided to miau from attached IRC-client.
*/
void
miau_commands(char *command, char *param, connection_type *client)
{
int corr = 0;
int i;
/* No command at all ? */
if (command == NULL) {
/*
* Basically we could just wait if-elseif-else to reach its
* end where we have this "(! corr)" -test, but xstrcmp
* wouldn't like NULL...
*/
irc_notice(client, status.nickname,
CLNT_COMMANDS);
return;
}
upcase(command);
if (xstrcmp(command, "REHASH") == 0) {
corr++;
rehash(0);
}
#ifdef INBOX
if (xstrcmp(command, "READ") == 0) {
corr++;
if (inbox != NULL && ftell(inbox) != 0) {
char s[1024];
char *r;
fflush(inbox);
rewind(inbox);
irc_notice(client, status.nickname, CLNT_INBOXSTART);
while (1) {
size_t size;
r = fgets(s, 1023, inbox);
if (r == NULL) {
break;
}
size = strlen(s);
if (s[size - 1] == '\n') {
s[size - 1] = '\0';
}
s[1023] = '\0';
irc_notice(client, status.nickname, "%s", s);
}
irc_notice(client, status.nickname, CLNT_INBOXEND);
fseek(inbox, 0, SEEK_END);
}
else {
irc_notice(client, status.nickname, CLNT_INBOXEMPTY);
}
}
else if (xstrcmp(command, "DEL") == 0) {
corr++;
if (inbox != NULL) {
fclose(inbox);
}
unlink(FILE_INBOX);
irc_notice(client, status.nickname, CLNT_KILLEDMSGS);
if (cfg.inbox == 1) {
inbox = fopen(FILE_INBOX, "w+");
if (inbox == NULL) {
report(MIAU_ERRINBOXFILE);
}
}
}
#endif /* ifdef INBOX */
else if (xstrcmp(command, "RESET") == 0) {
corr++;
irc_mnotice(&c_clients, status.nickname, MIAU_RESET);
server_reset();
}
#ifdef QUICKLOG
else if (xstrcmp(command, "FLUSHQLOG") == 0) {
time_t keep;
corr++;
keep = get_time(param);
qlog_flush(time(NULL) - keep * 60, 0);
if (keep == 0) {
irc_notice(client, status.nickname, MIAU_FLUSHQLOGALL);
} else {
int days, hours, minutes;
minutes = keep % 60;
hours = (keep / 60) % 24;
days = keep / 1440;
irc_notice(client, status.nickname, MIAU_FLUSHQLOG,
days, hours, minutes);
}
}
else if (xstrcmp(command, "QUICKLOG") == 0) {
time_t oldest;
corr++;
oldest = get_time(param);
qlog_check(oldest * 60);
qlog_replay_header(client);
qlog_replay(client, oldest * 60);
qlog_replay_footer(client);
}
#endif /* ifdef QUICKLOG */
#ifdef PINGSTAT
else if (xstrcmp(command, "PINGSTAT") == 0) {
if (ping_sent == 0) {
irc_notice(client, status.nickname, PING_NO_PINGS);
} else {
irc_notice(client, status.nickname, PING_STAT,
ping_sent, ping_got,
100 - (int) ((float) ping_got /
(float) ping_sent * 100.0));
}
corr++;
}
#endif /* ifdef PINGSTAT */
#ifdef FAKECMD
/* Developement only. */
else if (xstrcmp(command, "FAKECMD") == 0) {
corr++;
char *tbackup;
char *torigin;
char *tcommand;
char *tparam1;
char *tparam2;
int tcommandno;
tbackup = xstrdup(param);
torigin = strtok(param + 1, " ");
tcommand = strtok(NULL, " ");
tparam1 = strtok(NULL, " ");
tparam2 = strtok(NULL, "\0");
if (tcommand) {
tcommandno = atoi(tcommand);
if (tcommandno == 0) {
tcommandno = MINCOMMANDVALUE +
command_find(tcommand);
}
server_reply(tcommandno,
tbackup,
torigin,
tparam1,
tparam2,
&tcommandno);
}
xfree(tbackup);
}
#endif /* ifdef FAKECMD */
#ifdef UPTIME
else if (xstrcmp(command, "UPTIME") == 0) {
time_t now;
int seconds, minutes, hours, days;
corr++;
time(&now);
now -= status.startup;
getuptime(now, &days, &hours, &minutes, &seconds),
irc_notice(client, status.nickname, MIAU_UPTIME,
days, hours, minutes, seconds);
}
#endif /* ifdef UPTIME */
#ifdef DUMPSTATUS
else if (xstrcmp(command, "DUMP") == 0) {
corr++;
dump_status(0);
}
#endif /* ifdef DUMPSTATUS */
else if (xstrcmp(command, "STONED") == 0) {
server_drop("faked stoned server");
error("faked stoned server");
server_change(1, 0);
}
else if (xstrcmp(command, "JUMP") == 0) {
corr++;
/* Are there any other servers ? */
if (servers.amount != 0) {
server_drop(MIAU_JUMP);
report(MIAU_JUMP);
miau_welcome();
if (param != NULL) {
i = atoi(param);
if (i < 1) {
i = 1;
}
if (i >= servers.amount) {
i = servers.amount - 1;
}
i_server.current = servers.servers.head;
for (; i > 0; i--) {
i_server.current =
i_server.current->next;
}
((server_type *) i_server.current->data)
->working = 1;
/* we know what we're doing */
servers.fresh = 0;
server_change(0, 0);
} else {
/* Don't try this server again. :-) */
status.good_server = 0;
server_change(1,0);
}
/* Try connecting ASAP. */
timers.connect = cfg.reconnectdelay;
} else {
server_check_list();
}
}
else if (xstrcmp(command, "DIE") == 0) {
/*
* We need a copy of quit-reason because client-structures,
* where the original reason came from, is freed when we
* need it.
*/
char *reason;
reason = (param == NULL) ?
xstrdup(MIAU_DIE_CL) : xstrdup(param);
/*
* If user issues a DIE-command, (s)he most likely knows why
* connection was lost. Therefore we don't need to tell clients
* what's going on.
*/
/* (all clients, reason, notice, echo, no) */
drop_newclient(NULL);
client_drop(NULL, reason, DISCONNECT_DYING, 1, NULL);
server_drop(reason);
xfree(reason);
/*
* Finally free memory allocated by copy of user's command.
* (See client_free() for more info.
*/
client_free();
exit(EXIT_SUCCESS);
}
else if (xstrcmp(command, "PRINT") == 0) {
llist_node *node;
server_type *server;
int cur_ndx = -1;
int i = 1;
corr++;
/*
* We try to list the servers and do things like that - even
* if there aren't any servers. Oh well, at least I don't need
* to nest those lines... Lame. :-)
*
* In fact, we never try to view our fallback server.
* Perhaps we should ?
*/
irc_notice(client, status.nickname, (servers.amount != 1) ?
CLNT_SERVLIST : CLNT_NOSERVERS);
for (node = servers.servers.head->next; node != NULL;
node = node->next) {
server = (server_type *) node->data;
irc_notice(client, status.nickname,
"%c[%d] %s:%d%s (%d)",
(server->working ? '+' : '-' ),
i,
server->name,
server->port,
(server->password ? ":*" : ""),
(server->timeout == 0 ?
cfg.connecttimeout :
server->timeout));
if (node == i_server.current) {
cur_ndx = i;
}
i++;
}
if (servers.fresh == 0) {
irc_notice(client, status.nickname, CLNT_CURRENT,
cur_ndx,
(i_server.connected == 2) ?
"" : CLNT_ANDCONNECTING);
} else if (i_server.connected == 1) {
irc_notice(client, status.nickname, CLNT_CONNECTING);
}
}
if (! corr) {
irc_notice(client, status.nickname, CLNT_COMMANDS);
}
} /* void miau_commands(char *command, char *param, connection_type *client) */
/*
* Main loop.
*
* Checks if there's data coming from sockets etc.
*/
static void
run(void)
{
llist_node *client;
llist_node *client_next;
connection_type *client_conn;
server_type *server;
fd_set rfds, wfds;
struct timeval tv;
int selret;
FD_ZERO(&wfds); /* Needed if DCCBOUNCE is not defined. */
/* Do this 'till the end of days - until told otherwise. :-) */
while (1) {
if (! i_server.connected && /* Not connected. */
servers.amount > 1 && /* Servers on list. */
/* Time to connect. */
timers.connect > status.reconnectdelay) {
server = (server_type *) i_server.current->data;
server_set_fallback(i_server.current);
/* Not connected to server. */
report(SERV_TRYING, server->name, server->port);
servers.fresh = 0;
/* Assume we have our desired nick. */
xfree(status.nickname);
status.nickname = xstrdup((char *)
nicknames.nicks.head->data);
status.got_nick = 1;
nicknames.current = nicknames.nicks.head;
nicknames.next = NICK_NEXT;
/* Don't try connecting for too long... */
alarm(server->timeout == 0 ?
cfg.connecttimeout : server->timeout);
switch (irc_connect(&c_server, server,
(char *)
nicknames.nicks.head->data,
cfg.username,
cfg.realname, cfg.bind)) {
case CONN_OK:
report(SOCK_CONNECT, ((server_type *) i_server.current->data)->name);
i_server.connected = 1;
/* Clear good server -variables. */
status.good_server = 0;
timers.good_server = 0;
/* Clear getting_nick -counter. */
status.getting_nick = 0;
break;
case CONN_SOCK:
error(SOCK_ERROPEN, net_errstr);
exit(ERR_CODE_NETWORK);
break;
case CONN_LOOKUP:
error(SOCK_ERRRESOLVE, server->name);
server_drop(NULL);
server_change(1, 1);
break;
case CONN_BIND:
if (cfg.bind) {
error(SOCK_ERRBINDHOST,
cfg.bind,
cfg.listenport,
net_errstr);
} else {
error(SOCK_ERRBIND,
cfg.listenport,
net_errstr);
}
exit(ERR_CODE_NETWORK);
break;
case CONN_CONNECT:
/*
* If timer was triggered, socket is
* already closed and we don't need to
* display another error.
*/
if (c_server.socket != 0) {
error(SOCK_ERRCONNECT, server->name, strerror(errno));
sock_close(&c_server);
}
server_change(1, 1);
break;
case CONN_WRITE:
error(SOCK_ERRWRITE, server->name);
server_drop(NULL);
server_change(1, 0);
break;
case CONN_OTHER:
error(SOCK_GENERROR, net_errstr);
exit(ERR_CODE_NETWORK);
break;
default:
break;
}
alarm(0); /* Disable alarm. */
timers.connect = 0;
}
/* TODO: Changing FD_SETs only on demand would be nice... */
FD_ZERO(&rfds);
/* Listen for new clients. */
if (status.allowconnect && ! i_newclient.connected &&
cfg.maxclients > c_clients.connected) {
FD_SET(listensocket, &rfds);
}
/* Listen for the server we're connected to. */
if (i_server.connected) {
FD_SET(c_server.socket, &rfds);
}
/* Listen clients we already have. */
for (client = c_clients.clients->head; client != NULL;
client = client->next) {
FD_SET(((connection_type *) client->data)->socket,
&rfds);
}
/* Listen connecting client. */
if (i_newclient.connected) {
FD_SET(c_newclient.socket, &rfds);
}
#ifdef DCCBOUNCE
FD_ZERO(&wfds);
/* If others need this, it must be taken outside ifdef. */
if (cfg.dccbounce) {
dcc_socketsubscribe(&rfds, &wfds);
}
#endif /* ifdef DCCBOUNCE */
tv.tv_usec = 0;
tv.tv_sec = 1;
selret = select(highest_socket + 1, &rfds, NULL, NULL, &tv);
if (selret > 0) {
/* Data from server. */
if (FD_ISSET(c_server.socket, &rfds)) {
/* Server closed connection. */
if (server_read() < 0) {
server_drop(SERV_DROPPED);
error(SERV_DROPPED);
server_change(1, 0);
}
} /* Data from server. */
/* Connection from a new client. */
if (FD_ISSET(listensocket, &rfds)) {
status.allowconnect = 0;
xfree(i_newclient.hostname);
c_newclient.socket = sock_accept(listensocket,
&i_newclient.hostname, 1);
if (c_newclient.socket != -1) {
c_newclient.offset = 0;
i_newclient.connected = 1;
report(CLNT_CAUGHT,
i_newclient.hostname);
}
else {
/*
* Report unauthorized connections and
* connections that couldn't be
* accepted.
*/
if (c_newclient.socket) {
/* Bad nesting. */
report(CLNT_DENIED,
i_newclient.hostname);
}
/* Error while accepting. */
else {
/* Bad nesting. */
error(SOCK_ERRACCEPT,
i_newclient.hostname);
}
FREE(i_newclient.hostname);
c_newclient.socket = 0;
}
} /* Connection from new client. */
/* Data from connected client. */
client = c_clients.clients->head;
while (client != NULL) {
client_next = client->next;
client_conn = (connection_type *) client->data;
if (FD_ISSET(client_conn->socket, &rfds)) {
int ret;
ret = client_read(client_conn);
if (ret < 0) {
/* client closed connection */
/*
* (single client, message,
* report, no echo, no)
*/
client_drop(client_conn,
CLNT_DROPPED,
DISCONNECT_REPORT, 0,
NULL);
/* report(CLNT_DROPPED); */
}
}
client = client_next;
} /* Data from connected client. */
/* Data from new client. */
if (c_newclient.socket > 0 &&
FD_ISSET(c_newclient.socket, &rfds)) {
/* New client dropped connection. */
if (read_newclient() < 0) {
drop_newclient(NULL);
report(CLNT_DROPPEDUNAUTH);
}
} /* Data from new client. */
#ifdef DCCBOUNCE
if (cfg.dccbounce) {
dcc_socketcheck(&rfds, &wfds);
}
#endif /* ifdef DCCBOUNCE */
}
check_timers();
}
} /* static void run(void) */
static void
pre_init(void)
{
/* Initialize structure for nicknames. */
nicknames.nicks.head = NULL;
nicknames.nicks.tail = NULL;
/* Initialize some client-structures. */
c_clients.clients = (llist_list *) xmalloc(sizeof(llist_list));
c_clients.clients->head = NULL;
c_clients.clients->tail = NULL;
/* Create fallback server entry. */
add_server("", 0, NULL, 0);
/*
* Initially, we're not on channels nor there are any channels to
* be joined at attach.
*/
active_channels.head = NULL;
active_channels.tail = NULL;
passive_channels.head = NULL;
passive_channels.tail = NULL;
old_channels.head = NULL;
old_channels.tail = NULL;
#ifdef NEED_LOGGING
cfg.logsuffix = xstrdup("");
#endif /* ifdef NEED_LOGGING */
} /* static void pre_init(void) */
static void
init(void)
{
struct sigaction sv;
/* Create */
sigemptyset(&sv.sa_mask);
sv.sa_flags = 0;
sv.sa_handler = sig_term;
sigaction(SIGTERM, &sv, NULL);
sigaction(SIGINT, &sv, NULL);
/* sigaction(SIGKILL, &sv, NULL); -- shouldn't be catched... */
sv.sa_handler = connect_timeout;
sigaction(SIGALRM, &sv, NULL);
sv.sa_handler = rehash;
sigaction(SIGHUP, &sv, NULL);
/* clear timers */
memset(&timers, 0, sizeof(timers));
#ifdef DUMPSTATUS
sv.sa_handler = dump_status;
sigaction(SIGUSR2, &sv, NULL);
#endif /* ifdef DUMPSTATUS */
sv.sa_handler = SIG_IGN;
sigaction(SIGUSR1, &sv, NULL);
sigaction(SIGPIPE, &sv, NULL); /* Ignore SIGPIPEs for good. */
atexit(client_free); /* free temp memory for client_read */
atexit(free_resources); /* free mem taken by config etc. */
atexit(command_free); /* free mem taken by hash table */
umask(~S_IRUSR & ~S_IWUSR); /* For logfile(s). */
status.autojoindone = 0; /* Haven't joined cfg.channels yet. */
status.awaymsg = NULL; /* No non-default away message. */
status.awaystate = 0; /* No custom awaymsg | not away. */
status.allowreply = 1;
status.got_nick = 1; /* Assume everything's fine. */
status.getting_nick = 0; /* Not getting the nick right now. */
status.allowconnect = 1; /* We're listening for clients. */
#ifdef UPTIME
status.startup = 0; /* Uptime set to zero. */
#endif /* ifdef UPTIME */
/* We try the first nick when connecting anyway. */
nicknames.next = NICK_NEXT;
/* Start with first real server immediately. */
i_server.current = servers.servers.head->next;
timers.connect = cfg.reconnectdelay;
timers.good_server = 0;
status.good_server = 0;
status.goodhostname = 0;
#ifdef AUTOMODE
status.automodes = 0;
#endif /* ifdef AUTOMODE */
i_server.realname = xstrdup("miau");
srand(time(NULL));
create_listen();
#ifdef INBOX
if (cfg.inbox == 1) {
inbox = fopen(FILE_INBOX, "a+");
if (inbox == NULL) {
report(MIAU_ERRINBOXFILE);
}
}
#endif /* ifdef INBOX */
} /* static void init(void) */
/*
* Check configuration and report errors.
*
* Returns number of errors.
*/
static int
check_config(void)
{
int err = 0;
#define REPORTERROR(x) { error(PARSE_MK, x); err++; }
if (nicknames.nicks.head == NULL) REPORTERROR("nicknames");
if (cfg.realname == NULL) REPORTERROR("realname");
if (cfg.username == NULL) REPORTERROR("username");
if (cfg.password == NULL) REPORTERROR("password");
if (cfg.listenport == 0) REPORTERROR("listenport");
if (connhostlist.list.head == NULL) REPORTERROR("connhosts");
/*
* Actually, we did we do this?
*
if (! cfg.listenhost && cfg.bind) cfg.listenhost = xstrdup(cfg.bind);
*/
server_check_list();
if (servers.amount == 1) {
err++;
} else {
status.reconnectdelay = cfg.reconnectdelay;
}
return err;
} /* static int check_config(void) */
/*
* Setup home and create directories if they don't exist already.
*/
static void
setup_home(char *s)
{
if (s != NULL) {
int hsize;
hsize = strlen(s) + 2;
cfg.home = xmalloc(hsize);
snprintf(cfg.home, hsize, "%s/", s);
cfg.home[hsize - 1] = '\0';
}
else {
int hsize;
/*
* If user attacks miau with environment variables then
* so be it! It's his choice and his choice alone.
*/
s = getenv("HOME");
if (s == NULL) {
error(MIAU_ERRNOHOME);
exit(ERR_CODE_HOME);
}
hsize = strlen(s) + strlen(MIAUDIR) + 2;
cfg.home = xmalloc(hsize);
snprintf(cfg.home, hsize, "%s/%s", s, MIAUDIR);
cfg.home[hsize - 1] = '\0';
}
if (chdir(cfg.home) < 0) {
error(MIAU_ERRCHDIR, cfg.home);
exit(ERR_CODE_HOME);
}
} /* static void setup_home(char *s) */
static void
setup_atexit(void)
{
int r;
r = atexit(escape);
if (r != 0) {
error(ERR_CANT_ATEXIT);
escape();
exit(EXIT_FAILURE);
}
} /* static void setup_atexit(void) */
int
main(int argc, char **argv)
{
int pid = 0;
FILE *pidfile;
char *miaudir;
int dofork = 1;
int c;
printf("%s", BANNER);
miaudir = NULL;
opterr = 0;
/*
* getopt could have an overflow problem. If user runs miau with
* evil parameters, that his problem!
*/
while (1) {
#ifdef MKPASSWD
c = getopt(argc, argv, "cfd:v");
#else /* ifdef MKPASSWD */
c = getopt(argc, argv, "fd:v");
#endif /* ifdef else MKPASSWD */
if (c == -1) {
break;
}
switch (c) {
#ifdef MKPASSWD
case 'c':
{
char salt[3];
char *pass;
char *crypted;
salt[0] = '\0';
srand(time(NULL));
randname(salt, 2, ' ');
/*
* "The function getpass returns a
* pointer to a static buffer", but
* valgrind says "still reachable" but
* leaked anyhow.
*
* As a bonus: getpass may not be safe,
* but once we run it, we exit!
*/
pass = getpass(MIAU_ENTERPASS);
crypted = crypt(pass, salt);
printf(MIAU_THISPASS, crypted);
}
return EXIT_SUCCESS;
break;
#endif /* ifdef MKPASSWD */
case 'd':
miaudir = optarg;
break;
case 'f':
dofork = 0;
break;
case 'v':
exit(EXIT_SUCCESS);
case ':':
error(MIAU_ERRNEEDARG, optopt);
exit(EXIT_FAILURE);
break;
default:
printf("\n");
printf(SYNTAX, argv[0]);
return EXIT_FAILURE;
break;
}
}
pre_init();
setup_home(miaudir);
read_cfg();
if (check_config() != 0) {
exit(ERR_CODE_CONFIG);
}
command_setup();
create_dirs();
init();
if (dofork == 1) {
if (cfg.statelog == 1) {
/*
* See if file can be written to. If freopen fails,
* stdout would be lost. This check is not fool-proof,
* if someone changes the permissions before we get
* to call freopen, we will still fail. Lets consider
* this effort good enough.
*/
FILE *f;
f = fopen(FILE_LOG, "a");
if (f == NULL) {
error(MIAU_LOGNOWRITE, FILE_LOG);
exit(ERR_CODE_LOG);
}
fclose(f);
}
pid = fork();
if (pid < 0) {
error(MIAU_ERRFORK);
exit(EXIT_FAILURE);
}
if (pid == 0) {
/*
* Redirect stdout in file unless requested
* otherwise. Useful to disable this if you're
* low on disk space.
*/
if (cfg.statelog == 1) {
FILE *f;
f = freopen(FILE_LOG, "a", stdout);
if (f == NULL) {
error(MIAU_ERRFILE, cfg.home);
exit(ERR_CODE_HOME);
}
#ifndef SETVBUF_REVERSED
setvbuf(stdout, NULL, _IONBF, 0);
#else /* ifndef SETVBUF_REVERSED */
setvbuf(stdout, _IONBF, NULL, 0);
#endif /* ifndef else SETVBUF_REVERSED */
}
printf("\n");
report(MIAU_NEWSESSION);
report(MIAU_STARTINGLOG);
if (cfg.listenhost) {
report(SOCK_LISTENOKHOST, cfg.listenhost,
cfg.listenport);
} else {
report(SOCK_LISTENOK, cfg.listenport);
}
report(MIAU_NICK, (char *) nicknames.nicks.head->data);
setup_atexit();
run();
}
else { /* pid != 0 */
pidfile = fopen(FILE_PID, "w");
if (pidfile == NULL) {
error(MIAU_ERRFILE, cfg.home);
kill(pid, SIGTERM);
exit(ERR_CODE_HOME);
}
fprintf(pidfile, "%d\n", pid);
fclose(pidfile);
report(MIAU_NICK, (char *) nicknames.nicks.head->data);
report(MIAU_FORKED, pid);
}
} else {
setup_atexit();
run();
}
return EXIT_SUCCESS;
} /* int main(int argc, char **argv) */
syntax highlighted by Code2HTML, v. 0.9.1