/*
* chanprog.c -- handles:
* rmspace()
* maintaining the server list
* revenge punishment
* timers, utimers
* telling the current programmed settings
* initializing a lot of stuff and loading the tcl scripts
*
* $Id: chanprog.c,v 1.59 2006-03-28 02:35:49 wcc Exp $
*/
/*
* Copyright (C) 1997 Robey Pointer
* Copyright (C) 1999 - 2006 Eggheads Development Team
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "main.h"
#ifdef HAVE_GETRUSAGE
# include <sys/resource.h>
# ifdef HAVE_SYS_RUSAGE_H
# include <sys/rusage.h>
# endif
#endif
#ifdef HAVE_UNAME
# include <sys/utsname.h>
#endif
#include "modules.h"
extern struct userrec *userlist;
extern log_t *logs;
extern Tcl_Interp *interp;
extern char ver[], botnetnick[], firewall[], motdfile[], userfile[], helpdir[],
tempdir[], moddir[], notify_new[], owner[], configfile[];
extern time_t now, online_since;
extern int backgrd, term_z, con_chan, cache_hit, cache_miss, firewallport,
default_flags, max_logs, conmask, protect_readonly, make_userfile,
noshare, ignore_time;
tcl_timer_t *timer = NULL; /* Minutely timer */
tcl_timer_t *utimer = NULL; /* Secondly timer */
unsigned long timer_id = 1; /* Next timer of any sort will
* have this number */
struct chanset_t *chanset = NULL; /* Channel list */
char admin[121] = ""; /* Admin info */
char origbotname[NICKLEN + 1];
char botname[NICKLEN + 1]; /* Primary botname */
/* Remove leading and trailing whitespaces.
*/
void rmspace(char *s)
{
register char *p = NULL, *q = NULL;
if (!s || !*s)
return;
/* Remove trailing whitespaces. */
for (q = s + strlen(s) - 1; q >= s && egg_isspace(*q); q--);
*(q + 1) = 0;
/* Remove leading whitespaces. */
for (p = s; egg_isspace(*p); p++);
if (p != s)
memmove(s, p, q - p + 2);
}
/* Returns memberfields if the nick is in the member list.
*/
memberlist *ismember(struct chanset_t *chan, char *nick)
{
register memberlist *x;
for (x = chan->channel.member; x && x->nick[0]; x = x->next)
if (!rfc_casecmp(x->nick, nick))
return x;
return NULL;
}
/* Find a chanset by channel name as the server knows it (ie !ABCDEchannel)
*/
struct chanset_t *findchan(const char *name)
{
register struct chanset_t *chan;
for (chan = chanset; chan; chan = chan->next)
if (!rfc_casecmp(chan->name, name))
return chan;
return NULL;
}
/* Find a chanset by display name (ie !channel)
*/
struct chanset_t *findchan_by_dname(const char *name)
{
register struct chanset_t *chan;
for (chan = chanset; chan; chan = chan->next)
if (!rfc_casecmp(chan->dname, name))
return chan;
return NULL;
}
/*
* "caching" functions
*/
/* Shortcut for get_user_by_host -- might have user record in one
* of the channel caches.
*/
struct userrec *check_chanlist(const char *host)
{
char *nick, *uhost, buf[UHOSTLEN];
register memberlist *m;
register struct chanset_t *chan;
strncpyz(buf, host, sizeof buf);
uhost = buf;
nick = splitnick(&uhost);
for (chan = chanset; chan; chan = chan->next)
for (m = chan->channel.member; m && m->nick[0]; m = m->next)
if (!rfc_casecmp(nick, m->nick) && !egg_strcasecmp(uhost, m->userhost))
return m->user;
return NULL;
}
/* Shortcut for get_user_by_handle -- might have user record in channels
*/
struct userrec *check_chanlist_hand(const char *hand)
{
register struct chanset_t *chan;
register memberlist *m;
for (chan = chanset; chan; chan = chan->next)
for (m = chan->channel.member; m && m->nick[0]; m = m->next)
if (m->user && !egg_strcasecmp(m->user->handle, hand))
return m->user;
return NULL;
}
/* Clear the user pointers in the chanlists.
*
* Necessary when a hostmask is added/removed, a user is added or a new
* userfile is loaded.
*/
void clear_chanlist(void)
{
register memberlist *m;
register struct chanset_t *chan;
for (chan = chanset; chan; chan = chan->next)
for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
m->user = NULL;
m->tried_getuser = 0;
}
}
/* Clear the user pointer of a specific nick in the chanlists.
*
* Necessary when a hostmask is added/removed, a nick changes, etc.
* Does not completely invalidate the channel cache like clear_chanlist().
*/
void clear_chanlist_member(const char *nick)
{
register memberlist *m;
register struct chanset_t *chan;
for (chan = chanset; chan; chan = chan->next)
for (m = chan->channel.member; m && m->nick[0]; m = m->next)
if (!rfc_casecmp(m->nick, nick)) {
m->user = NULL;
m->tried_getuser = 0;
break;
}
}
/* If this user@host is in a channel, set it (it was null)
*/
void set_chanlist(const char *host, struct userrec *rec)
{
char *nick, *uhost, buf[UHOSTLEN];
register memberlist *m;
register struct chanset_t *chan;
strncpyz(buf, host, sizeof buf);
uhost = buf;
nick = splitnick(&uhost);
for (chan = chanset; chan; chan = chan->next)
for (m = chan->channel.member; m && m->nick[0]; m = m->next)
if (!rfc_casecmp(nick, m->nick) && !egg_strcasecmp(uhost, m->userhost))
m->user = rec;
}
/* Calculate the memory we should be using
*/
int expmem_chanprog()
{
register int tot = 0;
register tcl_timer_t *t;
for (t = timer; t; t = t->next)
tot += sizeof(tcl_timer_t) + strlen(t->cmd) + 1;
for (t = utimer; t; t = t->next)
tot += sizeof(tcl_timer_t) + strlen(t->cmd) + 1;
return tot;
}
/* Dump uptime info out to dcc (guppy 9Jan99)
*/
void tell_verbose_uptime(int idx)
{
char s[256], s1[121];
time_t now2, hr, min;
now2 = now - online_since;
s[0] = 0;
if (now2 > 86400) {
/* days */
sprintf(s, "%d day", (int) (now2 / 86400));
if ((int) (now2 / 86400) >= 2)
strcat(s, "s");
strcat(s, ", ");
now2 -= (((int) (now2 / 86400)) * 86400);
}
hr = (time_t) ((int) now2 / 3600);
now2 -= (hr * 3600);
min = (time_t) ((int) now2 / 60);
sprintf(&s[strlen(s)], "%02d:%02d", (int) hr, (int) min);
s1[0] = 0;
if (backgrd)
strcpy(s1, MISC_BACKGROUND);
else {
if (term_z)
strcpy(s1, MISC_TERMMODE);
else if (con_chan)
strcpy(s1, MISC_STATMODE);
else
strcpy(s1, MISC_LOGMODE);
}
dprintf(idx, "%s %s (%s)\n", MISC_ONLINEFOR, s, s1);
}
/* Dump status info out to dcc
*/
void tell_verbose_status(int idx)
{
char s[256], s1[121], s2[81];
char *vers_t, *uni_t;
int i;
time_t now2 = now - online_since, hr, min;
#ifdef HAVE_GETRUSAGE
struct rusage ru;
#else
# ifdef HAVE_CLOCK
clock_t cl;
# endif
#endif
#ifdef HAVE_UNAME
struct utsname un;
if (!uname(&un) < 0) {
#endif
vers_t = " ";
uni_t = "*unknown*";
#ifdef HAVE_UNAME
} else {
vers_t = un.release;
uni_t = un.sysname;
}
#endif
i = count_users(userlist);
dprintf(idx, "I am %s, running %s: %d user%s (mem: %uk).\n",
botnetnick, ver, i, i == 1 ? "" : "s",
(int) (expected_memory() / 1024));
s[0] = 0;
if (now2 > 86400) {
/* days */
sprintf(s, "%d day", (int) (now2 / 86400));
if ((int) (now2 / 86400) >= 2)
strcat(s, "s");
strcat(s, ", ");
now2 -= (((int) (now2 / 86400)) * 86400);
}
hr = (time_t) ((int) now2 / 3600);
now2 -= (hr * 3600);
min = (time_t) ((int) now2 / 60);
sprintf(&s[strlen(s)], "%02d:%02d", (int) hr, (int) min);
s1[0] = 0;
if (backgrd)
strcpy(s1, MISC_BACKGROUND);
else {
if (term_z)
strcpy(s1, MISC_TERMMODE);
else if (con_chan)
strcpy(s1, MISC_STATMODE);
else
strcpy(s1, MISC_LOGMODE);
}
#ifdef HAVE_GETRUSAGE
getrusage(RUSAGE_SELF, &ru);
hr = (int) ((ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) / 60);
min = (int) ((ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) - (hr * 60));
sprintf(s2, "CPU: %02d:%02d", (int) hr, (int) min); /* Actally min/sec */
#else
# ifdef HAVE_CLOCK
cl = (clock() / CLOCKS_PER_SEC);
hr = (int) (cl / 60);
min = (int) (cl - (hr * 60));
sprintf(s2, "CPU: %02d:%02d", (int) hr, (int) min); /* Actually min/sec */
# else
sprintf(s2, "CPU: unknown");
# endif
#endif
dprintf(idx, "%s %s (%s) - %s - %s: %4.1f%%\n", MISC_ONLINEFOR,
s, s1, s2, MISC_CACHEHIT,
100.0 * ((float) cache_hit) / ((float) (cache_hit + cache_miss)));
if (admin[0])
dprintf(idx, "Admin: %s\n", admin);
dprintf(idx, "Config file: %s\n", configfile);
dprintf(idx, "OS: %s %s\n", uni_t, vers_t);
/* info library */
dprintf(idx, "%s %s\n", MISC_TCLLIBRARY,
((interp) && (Tcl_Eval(interp, "info library") == TCL_OK)) ?
interp->result : "*unknown*");
/* info tclversion/patchlevel */
dprintf(idx, "%s %s (%s %s)\n", MISC_TCLVERSION,
((interp) && (Tcl_Eval(interp, "info patchlevel") == TCL_OK)) ?
interp->result : (Tcl_Eval(interp, "info tclversion") == TCL_OK) ?
interp->result : "*unknown*", MISC_TCLHVERSION,
TCL_PATCH_LEVEL ? TCL_PATCH_LEVEL : "*unknown*");
#ifdef HAVE_TCL_THREADS
dprintf(idx, "Tcl is threaded.\n");
#endif
}
/* Show all internal state variables
*/
void tell_settings(int idx)
{
char s[1024];
int i;
struct flag_record fr = { FR_GLOBAL, 0, 0, 0, 0, 0 };
dprintf(idx, "Botnet nickname: %s\n", botnetnick);
if (firewall[0])
dprintf(idx, "Firewall: %s:%d\n", firewall, firewallport);
dprintf(idx, "Userfile: %s\n", userfile);
dprintf(idx, "Motd: %s\n", motdfile);
dprintf(idx, "Directories:\n");
#ifndef STATIC
dprintf(idx, " Help : %s\n", helpdir);
dprintf(idx, " Temp : %s\n", tempdir);
dprintf(idx, " Modules: %s\n", moddir);
#else
dprintf(idx, " Help: %s\n", helpdir);
dprintf(idx, " Temp: %s\n", tempdir);
#endif
fr.global = default_flags;
build_flags(s, &fr, NULL);
dprintf(idx, "%s [%s], %s: %s\n", MISC_NEWUSERFLAGS, s,
MISC_NOTIFY, notify_new);
if (owner[0])
dprintf(idx, "%s: %s\n", MISC_PERMOWNER, owner);
for (i = 0; i < max_logs; i++)
if (logs[i].filename != NULL) {
dprintf(idx, "Logfile #%d: %s on %s (%s: %s)\n", i + 1,
logs[i].filename, logs[i].chname,
masktype(logs[i].mask), maskname(logs[i].mask));
}
dprintf(idx, "Ignores last %d minute%s.\n", ignore_time,
(ignore_time != 1) ? "s" : "");
}
void reaffirm_owners()
{
char *p, *q, s[121];
struct userrec *u;
/* Please stop breaking this function. */
if (owner[0]) {
q = owner;
p = strchr(q, ',');
while (p) {
strncpyz(s, q, (p - q) + 1);
rmspace(s);
u = get_user_by_handle(userlist, s);
if (u)
u->flags = sanity_check(u->flags | USER_OWNER);
q = p + 1;
p = strchr(q, ',');
}
strcpy(s, q);
rmspace(s);
u = get_user_by_handle(userlist, s);
if (u)
u->flags = sanity_check(u->flags | USER_OWNER);
}
}
void chanprog()
{
int i;
FILE *f;
char s[161], rands[8];
admin[0] = 0;
helpdir[0] = 0;
tempdir[0] = 0;
conmask = 0;
for (i = 0; i < max_logs; i++)
logs[i].flags |= LF_EXPIRING;
/* Turn off read-only variables (make them write-able) for rehash */
protect_readonly = 0;
/* Now read it */
if (!readtclprog(configfile))
fatal(MISC_NOCONFIGFILE, 0);
for (i = 0; i < max_logs; i++) {
if (logs[i].flags & LF_EXPIRING) {
if (logs[i].filename != NULL) {
nfree(logs[i].filename);
logs[i].filename = NULL;
}
if (logs[i].chname != NULL) {
nfree(logs[i].chname);
logs[i].chname = NULL;
}
if (logs[i].f != NULL) {
fclose(logs[i].f);
logs[i].f = NULL;
}
logs[i].mask = 0;
logs[i].flags = 0;
}
}
/* We should be safe now */
call_hook(HOOK_REHASH);
protect_readonly = 1;
if (!botnetnick[0])
strncpyz(botnetnick, origbotname, HANDLEN + 1);
if (!botnetnick[0])
fatal("I don't have a botnet nick!!\n", 0);
if (!userfile[0])
fatal(MISC_NOUSERFILE2, 0);
if (!readuserfile(userfile, &userlist)) {
if (!make_userfile) {
char tmp[178];
egg_snprintf(tmp, sizeof tmp, MISC_NOUSERFILE, configfile);
fatal(tmp, 0);
}
printf("\n\n%s\n", MISC_NOUSERFILE2);
if (module_find("server", 0, 0))
printf(MISC_USERFCREATE1, origbotname);
printf("%s\n\n", MISC_USERFCREATE2);
} else if (make_userfile) {
make_userfile = 0;
printf("%s\n", MISC_USERFEXISTS);
}
if (helpdir[0])
if (helpdir[strlen(helpdir) - 1] != '/')
strcat(helpdir, "/");
if (tempdir[0])
if (tempdir[strlen(tempdir) - 1] != '/')
strcat(tempdir, "/");
/* Test tempdir: it's vital. */
/* Possible file race condition solved by using a random string
* and the process id in the filename.
* FIXME: This race is only partitially fixed. We could still be
* overwriting an existing file / following a malicious
* link.
*/
make_rand_str(rands, 7); /* create random string */
sprintf(s, "%s.test-%u-%s", tempdir, getpid(), rands);
f = fopen(s, "w");
if (f == NULL)
fatal(MISC_CANTWRITETEMP, 0);
fclose(f);
unlink(s);
reaffirm_owners();
check_tcl_event("userfile-loaded");
}
/* Reload the user file from disk
*/
void reload()
{
if (!file_readable(userfile)) {
putlog(LOG_MISC, "*", MISC_CANTRELOADUSER);
return;
}
noshare = 1;
clear_userlist(userlist);
noshare = 0;
userlist = NULL;
if (!readuserfile(userfile, &userlist))
fatal(MISC_MISSINGUSERF, 0);
reaffirm_owners();
check_tcl_event("userfile-loaded");
call_hook(HOOK_READ_USERFILE);
}
void rehash()
{
call_hook(HOOK_PRE_REHASH);
noshare = 1;
clear_userlist(userlist);
noshare = 0;
userlist = NULL;
chanprog();
}
/*
* Brief venture into timers
*/
/* Add a timer
*/
unsigned long add_timer(tcl_timer_t ** stack, int elapse, char *cmd,
unsigned long prev_id)
{
tcl_timer_t *old = (*stack);
*stack = nmalloc(sizeof **stack);
(*stack)->next = old;
(*stack)->mins = elapse;
(*stack)->cmd = nmalloc(strlen(cmd) + 1);
strcpy((*stack)->cmd, cmd);
/* If it's just being added back and already had an id,
* don't create a new one.
*/
if (prev_id > 0)
(*stack)->id = prev_id;
else
(*stack)->id = timer_id++;
return (*stack)->id;
}
/* Remove a timer, by id
*/
int remove_timer(tcl_timer_t ** stack, unsigned long id)
{
tcl_timer_t *old;
int ok = 0;
while (*stack) {
if ((*stack)->id == id) {
ok++;
old = *stack;
*stack = ((*stack)->next);
nfree(old->cmd);
nfree(old);
} else
stack = &((*stack)->next);
}
return ok;
}
/* Check timers, execute the ones that have expired.
*/
void do_check_timers(tcl_timer_t ** stack)
{
tcl_timer_t *mark = *stack, *old = NULL;
char x[16];
/* New timers could be added by a Tcl script inside a current timer
* so i'll just clear out the timer list completely, and add any
* unexpired timers back on.
*/
*stack = NULL;
while (mark) {
if (mark->mins > 0)
mark->mins--;
old = mark;
mark = mark->next;
if (!old->mins) {
egg_snprintf(x, sizeof x, "timer%lu", old->id);
do_tcl(x, old->cmd);
nfree(old->cmd);
nfree(old);
} else {
old->next = *stack;
*stack = old;
}
}
}
/* Wipe all timers.
*/
void wipe_timers(Tcl_Interp *irp, tcl_timer_t **stack)
{
tcl_timer_t *mark = *stack, *old;
while (mark) {
old = mark;
mark = mark->next;
nfree(old->cmd);
nfree(old);
}
*stack = NULL;
}
/* Return list of timers
*/
void list_timers(Tcl_Interp *irp, tcl_timer_t *stack)
{
char mins[10], id[16], *x;
EGG_CONST char *argv[3];
tcl_timer_t *mark;
for (mark = stack; mark; mark = mark->next) {
egg_snprintf(mins, sizeof mins, "%u", mark->mins);
egg_snprintf(id, sizeof id, "timer%lu", mark->id);
argv[0] = mins;
argv[1] = mark->cmd;
argv[2] = id;
x = Tcl_Merge(3, argv);
Tcl_AppendElement(irp, x);
Tcl_Free((char *) x);
}
}
/* Oddly enough, written by Sup (former(?) Eggdrop coder)
*/
int isowner(char *name)
{
register char *ptr = NULL, *s = NULL, *n = NULL;
if (!owner || !name)
return 0;
ptr = owner - 1;
do {
ptr++;
if (*ptr && !egg_isspace(*ptr) && *ptr != ',') {
if (!s)
s = ptr;
} else if (s) {
for (n = name; *n && *s && s < ptr && tolower(*n) == tolower(*s); n++, s++);
if (s == ptr && !*n)
return 1;
s = NULL;
}
} while (*ptr);
return 0;
}