bitchx/source/irc.c

1652 lines
37 KiB
C

/*
* ircII: a new irc client. I like it. I hope you will too!
*
* Written By Michael Sandrof
* Copyright(c) 1990
* See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
*/
#define __irc_c
#include "irc.h"
#include "struct.h"
static char cvsrevision[] = "$Id$";
CVS_REVISION(irc_c)
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#ifdef USING_CURSES
#include <curses.h>
#endif
#include <stdarg.h>
#include "status.h"
#include "dcc.h"
#include "names.h"
#include "vars.h"
#include "input.h"
#include "alias.h"
#include "output.h"
#include "ircterm.h"
#include "exec.h"
#include "flood.h"
#include "screen.h"
#include "log.h"
#include "server.h"
#include "hook.h"
#include "keys.h"
#include "ircaux.h"
#include "commands.h"
#include "window.h"
#include "history.h"
#include "exec.h"
#include "notify.h"
#include "mail.h"
#include "debug.h"
#include "newio.h"
#include "timer.h"
#include "whowas.h"
#include "misc.h"
#include "gui.h"
#include "cdns.h"
#include "tcl_bx.h"
#include "ssl.h"
#include <pwd.h>
#define MAIN_SOURCE
#include "modval.h"
#ifdef __EMX__
#include <signame.h>
#endif
const char irc_version[] = VERSION;
/* Format of bitchx_numver: MMmmpp
* MM = major version (eg 10 = 1.0)
* mm = minor version
* pp = patchlevel (00 = development, 01 = release)
*/
const unsigned long bitchx_numver = 120101;
/*
* INTERNAL_VERSION is the number that the special alias $V returns.
* Make sure you are prepared for floods, pestilence, hordes of locusts(
* and all sorts of HELL to break loose if you change this number.
* Its format is actually YYYYMMDD, for the _release_ date of the
* client..
*/
const char internal_version[] = "20141114";
int irc_port = IRC_PORT, /* port of ircd */
strip_ansi_in_echo,
current_on_hook = -1, /* used in the send_text()
* routine */
use_flow_control = USE_FLOW_CONTROL, /* true: ^Q/^S used for flow
* cntl */
current_numeric, /* this is negative of the
* current numeric! */
dumb_mode = 0, /* if true, IRCII is put in
* "dumb" mode */
bflag = 1,
use_input = 1, /* if 0, stdin is never
* checked */
waiting_out = 0, /* used by /WAIT command */
waiting_in = 0, /* used by /WAIT command */
who_mask = 0, /* keeps track of which /who
* switches are set */
dead = 0,
inhibit_logging = 0,
#ifndef ONLY_STD_CHARS
startup_ansi = 1, /* display startup ansi */
#else
startup_ansi = 0, /* DO NOT display startup ansi */
#endif
auto_connect = 1, /* auto-connect to first server*/
background = 0,
do_check_pid = 0,
do_ignore_ajoin = 0,
#ifdef HAVE_LIBSSL
do_use_ssl = 0,
#endif
run_level = 0,
foreground = 1,
reconnect = 0, /* reconnecting to old process */
use_nat_address = 0, /* use NAT address */
term_initialized = 0;
char zero[] = "0",
one[] = "1",
space[] = " ",
space_plus[] = " +",
space_minus[] = " -",
dot[] = ".",
star[] = "*",
comma[] = ",",
empty_string[] = "",
on[] = "ON",
off[] = "OFF";
const char *unknown_userhost = "<UNKNOWN>@<UNKNOWN>";
char oper_command = 0; /* true just after an oper() command is
* given. Used to tell the difference
* between an incorrect password generated by
* an oper() command and one generated when
* connecting to a new server */
struct sockaddr_foobar MyHostAddr; /* The local machine address */
struct sockaddr_foobar LocalHostAddr;
char *LocalHostName = NULL;
char *channel = NULL;
int inbound_line_mangler = 0,
logfile_line_mangler = 0,
operlog_line_mangler = 0,
outbound_line_mangler = 0;
char *invite_channel = NULL, /* last channel of an INVITE */
*ircrc_file = NULL, /* full path .ircrc file */
*bircrc_file = NULL, /* full path .bitchxrc file */
*my_path = NULL, /* path to users home dir */
*irc_path = NULL, /* paths used by /load */
*irc_lib = NULL, /* path to the ircII library */
*ircservers_file = NULL, /* name of server file */
nickname[NICKNAME_LEN + 1], /* users nickname */
hostname[NAME_LEN + 1], /* name of current host */
userhost[(NAME_LEN + 1) * 2],
realname[REALNAME_LEN + 1], /* real name of user */
username[NAME_LEN + 1], /* usernameof user */
attach_ttyname[500], /* ttyname for this term */
*forwardnick = NULL, /* used for /forward */
*send_umode = NULL, /* sent umode */
*args_str = NULL, /* list of command line args */
*last_notify_nick = NULL, /* last detected nickname */
*auto_str = NULL, /* auto response str */
*new_script = NULL, /* rephacement for .bitchxrc and .ircrc */
*cut_buffer = NULL; /* global cut_buffer */
int quick_startup = 0; /* set if we ignore .ircrc */
int cpu_saver = 0;
int use_socks = 0; /* do we use socks info to connect */
char *old_tty = NULL, /* re-attach tty and password */
*old_pass = NULL;
extern char *FromUserHost;
int cx_line = 0;
char cx_file[BIG_BUFFER_SIZE/4]; /* debug file info */
char cx_function[BIG_BUFFER_SIZE/4];
extern int doing_privmsg, doing_notice;
time_t idle_time = 0,
now = 0,
start_time;
fd_set readables, writables;
struct in_addr nat_address;
static void quit_response (char *, char *);
static char *parse_args (char **, int, char **);
static void remove_pid(void);
void do_ansi_logo(int);
extern void set_process_bits(fd_set *rd);
static volatile int cntl_c_hit = 0;
char version[] = _VERSION_;
static const char * const switch_help =
"Usage: BitchX [switches] [nickname] [server list] \n"
" The [nickname] can be at most 15 characters long.\n"
" The [server list] is a whitespace-separated list of server descriptions. Each\n"
" server description is of the form hostname[:port[:password[:nick[:network]]]].\n"
" The [switches] may be any or all of the following:\n"
#ifndef WINNT
" -H <hostname>\tuses the virtual hostname if possible\n"
#endif
" -N do not auto-connect to the first server\n"
" -A do not display the startup ansi\n"
" -c <channel>\tjoins <channel> on startup. don\'t forget to escape the # using \\\n"
" -b\t\tload " BITCHXRC_NAME " or " IRCRC_NAME " after connecting to a server\n"
" -p <port>\tdefault server connection port (usually 6667)\n"
#ifndef WINNT
" -f\t\tyour terminal uses flow controls (^S/^Q), so BitchX shouldn't\n"
" -F\t\tyour terminal doesn't use flow control (default)\n"
#endif
" -d\t\truns BitchX in \"dumb\" terminal mode\n"
" -q\t\tdoes not load ~/" IRCRC_NAME "\n"
" -r file\tload file as list of servers\n"
" -n nickname\tnickname to use\n"
" -a\t\tadds default servers and command line servers to server list\n"
" -x\t\truns BitchX in \"debug\" mode\n"
" -Z\t\tuse NAT address when doing DCC\n"
" -P\t\ttoggle check pid.nickname for running program\n"
" -v\t\ttells you about the client's version\n"
#ifdef HAVE_LIBSSL
" -s\t\tnext server specified is SSL (may be used multiple times)\n"
#endif
" -i\t\tignores the autojoin list entries\n"
" -l <file>\tloads <file> in place of your " IRCRC_NAME "\n"
" -L <file>\tloads <file> in place of your " IRCRC_NAME " and expands $ expandos\n"
#if !defined(WINNT) && !defined(__EMX__)
" -B\t\tforce BitchX to fork and return you to shell. pid check on.\n"
#endif
;
char *time_format = NULL; /* XXX Bogus XXX */
#ifdef __EMX__
char *strftime_24hour = "%H:%M";
#else
char *strftime_24hour = "%R";
#endif
char *strftime_12hour = "%I:%M%p";
char time_str[61];
void detachcmd(char *, char *, char *, char *);
/* update_clock: figUres out the current time and returns it in a nice format */
char *BX_update_clock(int flag)
{
static int min = -1,
hour = -1;
static time_t last_minute = -1;
time_t idlet;
static struct tm time_val;
time_t hideous;
int new_minute = 0;
int new_hour = 0;
hideous = now;
#if !defined(NO_CHEATING)
if (hideous / 60 > last_minute)
{
last_minute = hideous / 60;
time_val = *localtime(&hideous);
}
#else
time_val = *localtime(&hideous);
#endif
if (flag == RESET_TIME || time_val.tm_min != min || time_val.tm_hour != hour)
{
int ofs = from_server;
from_server = primary_server;
if (time_format) /* XXXX Bogus XXXX */
strftime(time_str, 60, time_format, &time_val);
else if (get_int_var(CLOCK_24HOUR_VAR))
strftime(time_str, 60, strftime_24hour, &time_val);
else
strftime(time_str, 60, strftime_12hour, &time_val);
lower(time_str);
if ((time_val.tm_min != min) || (time_val.tm_hour != hour))
{
int is_away = 0;
if (time_val.tm_hour != hour)
new_hour = 1;
new_minute = 1;
hour = time_val.tm_hour;
min = time_val.tm_min;
do_hook(TIMER_LIST, "%02d:%02d", hour, min);
if (min == 0 || new_hour)
do_hook(TIMER_HOUR_LIST, "%02d:%02d", hour, min);
idlet = hideous - idle_time;
if (from_server != -1)
is_away = get_server_away(from_server) ? 1 : 0;
if (do_hook(IDLE_LIST, "%lu", (unsigned long)idlet / 60))
{
if (is_away && new_hour && get_int_var(TIMESTAMP_AWAYLOG_HOURLY_VAR))
logmsg(LOG_CRAP, NULL, 4, NULL);
check_auto_away(idlet);
}
check_channel_limits();
}
from_server = ofs;
if (flag != RESET_TIME || new_minute)
return time_str;
else
return NULL;
}
if (flag == GET_TIME)
return time_str;
else
return NULL;
}
void reset_clock(Window *win, char *unused, int unused1)
{
update_clock(RESET_TIME);
update_all_status(win, NULL, 0);
}
/* sig_refresh_screen: the signal-callable version of refresh_screen */
static SIGNAL_HANDLER(sig_refresh_screen)
{
refresh_screen(0, NULL);
}
/* irc_exit: cleans up and leaves */
static SIGNAL_HANDLER(irc_exit_old)
{
irc_exit(1,NULL, NULL);
}
volatile int dead_children_processes;
/* This is needed so that the fork()s we do to read compressed files don't
* sit out there as zombies and chew up our fds while we read more.
*/
SIGNAL_HANDLER(child_reap)
{
dead_children_processes++;
#ifdef __EMX__
my_signal(SIGCHLD, SIG_IGN, 0);
#endif
}
static SIGNAL_HANDLER(nothing)
{
/* nothing to do! */
}
SIGNAL_HANDLER(sigpipe)
{
static int sigpipe_hit = 0;
sigpipe_hit++;
}
#if 0
static SIGNAL_HANDLER(sigusr2)
{
mtrace();
}
static SIGNAL_HANDLER(sigusr3)
{
muntrace();
}
#endif
/* irc_exit: cleans up and leaves */
void BX_irc_exit (int really_quit, char *reason, char *format, ...)
{
char buffer[BIG_BUFFER_SIZE];
logger(current_window, NULL, 0);
if (get_int_var(MSGLOG_VAR))
log_toggle(0, NULL);
if (format)
{
va_list arglist;
va_start(arglist, format);
vsprintf(buffer, format, arglist);
va_end(arglist);
}
else
sprintf(buffer, "%s -- just do it.",irc_version);
if (really_quit)
{
put_it("%s", convert_output_format("$G Signon time : $0-", "%s", my_ctime(start_time)));
put_it("%s", convert_output_format("$G Signoff time : $0-", "%s", my_ctime(now)));
put_it("%s", convert_output_format("$G Total uptime : $0-", "%s", convert_time(now - start_time)));
}
do_hook(EXIT_LIST, "%s", reason ? reason : buffer);
close_all_servers(reason ? reason : buffer);
put_it("%s", !format && reason ? reason : buffer);
clean_up_processes();
if (!dumb_mode && term_initialized)
{
cursor_to_input(); /* Needed so that ircII doesn't gobble
* the last line of the kill. */
term_cr();
term_clear_to_eol();
term_reset();
}
destroy_call_stack();
#if defined(THREAD) && defined(WANT_NSLOOKUP)
kill_dns();
#endif
remove_pid();
if (really_quit)
{
#ifdef GUI
gui_exit();
#else
#if defined(WANT_DETACH) && !defined(GUI)
kill_attached_if_needed(0);
#endif
fprintf(stdout, "\r");
fflush(stdout);
exit(0);
#endif
}
}
#ifndef WANT_CORE
volatile int segv_recurse = 0;
/* sigsegv: something to handle segfaults in a nice way */
/* this needs to be changed to *NOT* use printf(). */
static SIGNAL_HANDLER(coredump)
{
#if defined(WINNT)
extern char *sys_siglist[];
#endif
if (segv_recurse)
_exit(1);
segv_recurse = 1;
panic_dump_call_stack();
#ifdef TDEBUG
putlog(LOG_ALL, "*", "Error logged. %s at (%d) %s", cx_file, cx_line, cx_function?cx_function:empty_string);
#endif
printf("\n\r\n\rIRCII has been terminated by a [%s]\n\r", sys_siglist[unused]);
printf("Please email " BUG_EMAIL "\n\r");
printf("with as much detail as possible about what you were doing when it happened.\n\r");
printf("Please include the version of IRCII (%s) and type of system in the report.\n\r", irc_version);
fflush(stdout);
irc_exit(1, "Hmmmm... BitchX error!!!! unusual :)", NULL);
}
#endif
/*
* quit_response: Used by irc_io when called from irc_quit to see if we got
* the right response to our question. If the response was affirmative, the
* user gets booted from irc. Otherwise, life goes on.
*/
static void quit_response(char *dummy, char *ptr)
{
int len;
if ((len = strlen(ptr)) != 0)
if (!my_strnicmp(ptr, "yes", len))
irc_exit(1, NULL, "IRC][ %s: Rest in peace", irc_version);
}
/* irc_quit: prompts the user if they wish to exit, then does the right thing */
void irc_quit(char key, char * ptr)
{
static int in_it = 0;
if (in_it)
return;
in_it = 1;
add_wait_prompt("Do you really want to quit? ", quit_response, empty_string, WAIT_PROMPT_LINE, 1);
in_it = 0;
}
/*
* cntl_c: emergency exit.... if somehow everything else freezes up, hitting
* ^C five times should kill the program.
*/
static SIGNAL_HANDLER(cntl_c)
{
if (cntl_c_hit++ >= 4)
irc_exit(1, "User abort with 5 Ctrl-C's", NULL);
else if (cntl_c_hit > 1)
kill(getpid(), SIGALRM);
}
static SIGNAL_HANDLER(sig_user1)
{
bitchsay("Got SIGUSR1, closing DCC connections and EXECed processes");
close_all_dcc();
clean_up_processes();
}
static SIGNAL_HANDLER(sig_detach)
{
detachcmd(NULL, NULL, NULL, NULL);
}
void set_detach_on_hup(Window *dummy, char *unused, int value)
{
if(value)
my_signal(SIGHUP, sig_detach, 0);
else
my_signal(SIGHUP, irc_exit_old, 0);
}
#ifndef RAND_MAX
#define RAND_MAX 2147483647
#endif
void display_bitchx(int j)
{
int i = 0;
int old_strip_ansi = strip_ansi_in_echo;
strip_ansi_in_echo = 0;
if (j == -1)
#ifdef ASCII_LOGO
i = (int) (5.0*rand()/RAND_MAX);
#else
i = (int) (17.0*rand()/RAND_MAX);
#endif
else
i = j;
if (!startup_ansi)
return;
#if !defined(WINNT) && !defined(__EMX__)
charset_ibmpc();
#endif
do_ansi_logo(i);
#if !defined(WINNT) && !defined(__EMX__)
#if defined(LATIN1)
charset_lat1();
#elif defined(CHARSET_CUSTOM)
charset_cst();
#endif
#endif
strip_ansi_in_echo = old_strip_ansi;
}
/*
* parse_args: parse command line arguments for irc, and sets all initial
* flags, etc.
*
* major rewrite 12/22/94 -jfn
*
*
* I'm going to break backwards compatibility here: I think that I'm
* safer in doing this because there are a lot less shell scripts with
* the command line flags then there are ircII scripts with old commands/
* syntax that would be a nasty thing to break..
*
* Sanity check:
* Supported flags: -b, -l, -v, -c, -p, -f, -F, -L, -a, -S, -z
* New (changed) flags: -s, -I, -i, -n
*
* Rules:
* Each flag must be included by a hyphen: -lb <filename> is not the
* same as -l <filename> -b any more...
* Each flag may or may not have a space between the flag and the argument.
* -lfoo is the same as -l foo
* Anything surrounded by quotation marks is honored as one word.
* The -c, -p, -L, -l, -s, -z flags all take arguments. If no arguments
* are given between the flag and the next flag, an error
* message is printed and the program is halted.
* Exception: the -s flag will be accepted without an argument.
* (ick: backwards compatibility sucks. ;-)
* Arguments occurring after a flag that does not take an argument
* will be parsed in the following way: the first instance
* will be an assumed nickname, and the second instance will
* will be an assumed server. (some semblance of back compat.)
* The -bl sequence will emit a depreciated feature warning.
* The -I flag forces you to become invisible <NOT YET SUPPORTED>
* The -i flag forces you to become visible <NOT YET SUPPORTED>
* The -X flag forces ircII to become an X application <NOT YET SUPPORTED>
* The -n flag means "nickname"
*
* Bugs:
* The -s flag is hard to use without an argument unless you're careful.
*/
#ifdef CLOAKED
extern char **Argv;
extern char *LastArgv;
#endif
static char *parse_args (char *argv[], int argc, char **envp)
{
int ac;
int add_servers = 0;
#if !defined(WINNT)
struct passwd *entry;
#endif
char *channel = NULL;
char *ptr;
*nickname = 0;
#ifdef CLOAKED
Argv = argv;
while (*envp)
envp++;
LastArgv = envp[-1] + strlen(envp[-1]);
#endif
for ( ac = 1; ac < argc; ac++ )
{
if (argv[ac][0] == '-')
{
switch (argv[ac][1]) {
case 'A': /* turn off startup ansi */
startup_ansi = 0;
break;
case 'v': /* Output client version (already done) and exit */
exit(0);
case 'c': /* Default channel to join */
{
char *what = empty_string;
if (argv[ac][2])
what = &(argv[ac][2]);
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
{
fprintf(stderr, "Missing parameter after -c\n");
exit(1);
}
malloc_strcpy(&channel, what);
break;
}
case 'p': /* Default port to use */
{
char *what = empty_string;
if (reconnect)
{
what = getpass("Enter password : ");
if (what && *what)
malloc_strcpy(&old_pass, what);
break;
}
if (argv[ac][2])
what = &argv[ac][2];
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
{
fprintf(stderr, "Missing parameter after -p\n");
exit(1);
}
irc_port = my_atol(what);
break;
}
#ifndef WINNT
case 'f': /* Use flow control */
{
use_flow_control = 1;
if (argv[ac][2])
fprintf(stderr, "Ignoring junk after -f\n");
break;
}
case 'F': /* don't use flow control */
{
use_flow_control = 0;
if (argv[ac][2])
fprintf(stderr, "Ignoring junk after -F\n");
break;
}
#endif
case 'd': /* use dumb mode */
{
dumb_mode = 1;
if (argv[ac][2])
fprintf(stderr, "Ignoring junk after -d\n");
break;
}
case 'l': /* Load some file instead of ~/.ircrc */
{
char *what = empty_string;
if (argv[ac][2])
what = &argv[ac][2];
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
{
fprintf(stderr, "Missing argument to -l\n");
exit(1);
}
malloc_strcpy(&new_script, what);
break;
}
case 'L': /* load and expand */
{
char *what = empty_string;
if (argv[ac][2])
what = &argv[ac][2];
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
{
fprintf(stderr, "Missing argument to -L\n");
exit(1);
}
malloc_strcpy(&ircrc_file, what);
/* malloc_strcat(&ircrc_file, " -");*/
break;
}
case 'r': /* Load list of servers from this file */
{
char *what = empty_string;
if (argv[ac][2])
what = &argv[ac][2];
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
fprintf(stderr, "Missing argumenT to -r\n");
if (*what)
{
add_servers = 1;
malloc_strcpy(&ircservers_file, what);
}
break;
}
case 'R':
fprintf(stderr, "Use \"scr-bx\" to reconnect\r\n");
exit(1);
break;
case 'a': /* add server, not replace */
{
add_servers = 1;
if (argv[ac][2])
fprintf(stderr, "Ignoring junk after -a\n");
break;
}
case 'q': /* quick startup -- no .ircrc */
{
quick_startup = 1;
if (argv[ac][2])
fprintf(stderr, "Ignoring junk after -q\n");
break;
}
case 'b':
{
bflag = 0;
break;
}
case 'B':
{
if (argv[ac][2] && argv[ac][2] != 'l')
fprintf(stderr, "Ignoring junk after -B\n");
else if (argv[ac][2] == 'l')
{
fprintf(stderr, "Usage of -bl is decprecated: use -b -l instead.\n");
exit(1);
}
/* dumb_mode = 1;*/
/* use_input = 0;*/
background = 1;
}
case 'P':
{
do_check_pid ^= 1;
break;
}
case 'n':
{
char *what = empty_string;
if (argv[ac][2])
what = &(argv[ac][2]);
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
{
fprintf(stderr,"Missing argument for -n\n");
exit(1);
}
strlcpy(nickname, what, sizeof nickname);
break;
}
case 'N':
{
auto_connect = 0;
break;
}
case 'x': /* set server debug */
{
x_debug = (unsigned long)0xffffffff;
if (argv[ac][2])
fprintf(stderr, "Ignoring junk after -x\n");
break;
}
case 'Z':
{
use_nat_address = 1;
break;
}
case 'z':
{
char *what;
if (argv[ac][2])
what = &argv[ac][2];
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
break;
strlcpy(username, what, sizeof username);
break;
}
case 'H':
{
char *what = empty_string;
if (argv[ac][2])
what = &(argv[ac][2]);
else if (argv[ac+1] && argv[ac+1][0] != '-')
{
what = argv[ac+1];
ac++;
}
else
{
fprintf(stderr, "You forgot to specify a hostname\n");
exit(1);
}
malloc_strcpy(&LocalHostName, what);
break;
}
case 'S':
use_socks = 1;
break;
case 'i':
do_ignore_ajoin = 1;
break;
#ifdef HAVE_LIBSSL
case 's':
do_use_ssl = 1;
break;
#endif
case '\0':
break; /* ignore - alone */
default:
fprintf(stderr, "Unknown flag: %s\n",argv[ac]);
case 'h':
fputs(switch_help, stderr);
exit(1);
} /* End of switch */
}
else
{
if (!strchr(argv[ac], '.') && !strchr(argv[ac], ',') && !*nickname)
strlcpy(nickname, argv[ac], sizeof nickname);
else
{
build_server_list(argv[ac]);
#ifdef HAVE_LIBSSL
/* -s flag only applies to next server specified */
do_use_ssl = 0;
#endif
}
}
}
if (!*nickname && (ptr = getenv("IRCNICK")))
strlcpy(nickname, ptr, sizeof nickname);
if (!ircservers_file)
#if defined(WINNT) || defined(__EMX__)
malloc_strcpy(&ircservers_file, "irc-serv");
#else
malloc_strcpy(&ircservers_file, ".ircservers");
#endif
/* v-- right there was a '!' that should not have been there. */
if ((ptr = getenv("IRCLIB")))
{
malloc_strcpy(&irc_lib, ptr);
malloc_strcat(&irc_lib, "/");
}
else
malloc_strcpy(&irc_lib, IRCLIB);
if (!ircrc_file && (ptr = getenv("IRCRC")))
malloc_strcpy(&ircrc_file, ptr);
if ((ptr = getenv("IRCUMODE")))
malloc_strcpy(&send_umode, ptr);
if ((ptr = getenv("IRCNAME")))
strlcpy(realname, ptr, sizeof realname);
else if ((ptr = getenv("NAME")))
strlcpy(realname, ptr, sizeof realname);
if ((ptr = getenv("IRCPATH")))
malloc_strcpy(&irc_path, ptr);
else
{
#ifdef IRCPATH
malloc_strcpy(&irc_path, IRCPATH);
#else
malloc_strcpy(&irc_path, ".:~/.irc:");
malloc_strcat(&irc_path, irc_lib);
malloc_strcat(&irc_path, "script");
#endif
}
set_string_var(LOAD_PATH_VAR, irc_path);
new_free(&irc_path);
/*
* Yes... this is EXACTLY what you think it is. And if you don't know..
* then I'm not about to tell you! -- Jake [WinterHawk] Khuon
*
* Here we determine our username. It may be set above, by the -z command line
* option. If not check IRCUSER, then USER, then the IDENT_HACK file, then
* fallback to gecos below.
*/
if (!*username && (ptr = getenv("IRCUSER"))) strlcpy(username, ptr, sizeof username);
else if (!*username && (ptr = getenv("USER"))) strlcpy(username, ptr, sizeof username);
else if (!*username)
{
#ifdef IDENT_FAKE
char *p = NULL, *q = NULL;
FILE *f;
malloc_sprintf(&p, "~/%s", get_string_var(IDENT_HACK_VAR));
q = expand_twiddle(p);
if ((f = fopen(q, "r")))
{
fgets(username, NAME_LEN, f);
if (*username && strchr(username, '\n'))
username[strlen(username)-1] = 0;
}
fclose(f);
new_free(&p); new_free(&q);
if (!*username)
#endif
strlcpy(username, "Unknown", sizeof username);
}
#ifndef WINNT
if ((entry = getpwuid(getuid())))
{
if (!*realname && entry->pw_gecos && *(entry->pw_gecos))
{
#ifdef GECOS_DELIMITER
if ((ptr = strchr(entry->pw_gecos, GECOS_DELIMITER)))
*ptr = 0;
#endif
/* The first '&' character in pw_gecos is replaced with pw_name */
if ((ptr = strchr(entry->pw_gecos, '&')))
*ptr = 0;
strlcpy(realname, entry->pw_gecos, sizeof realname);
if (ptr)
{
size_t len = ptr - entry->pw_gecos;
strlcat(realname, entry->pw_name, sizeof realname);
strlcat(realname, ptr + 1, sizeof realname);
/* Make the first character of the username uppercase, if
it's preceded by a space */
if (len < sizeof realname && *(entry->pw_name) &&
(len == 0 || isspace((unsigned char)realname[len - 1])))
{
realname[len] = toupper((unsigned char)realname[len]);
}
}
}
if (entry->pw_name && *(entry->pw_name) && !*username)
strlcpy(username, entry->pw_name, sizeof username);
if (entry->pw_dir && *(entry->pw_dir))
malloc_strcpy(&my_path, entry->pw_dir);
}
#else
{
u_long size=NAME_LEN+1;
if (!(ptr = getenv("IRCUSER")))
strcpy(username, "unknown");
else
strncpy(username,ptr, size);
}
#endif
if ((ptr = getenv("HOME")))
malloc_strcpy(&my_path, ptr);
else if (!my_path || !*my_path)
#ifdef WINNT
malloc_strcpy(&my_path, empty_string);
#else
malloc_strcpy(&my_path, "/");
#endif
#if defined(WINNT) || defined(__EMX__)
convert_unix(my_path);
#endif
if (!*realname)
strlcpy(realname, "* I'm too lame to read BitchX.doc *", sizeof realname);
if (!LocalHostName && ((ptr = getenv("IRC_HOST")) || (ptr = getenv("IRCHOST"))))
LocalHostName = m_strdup(ptr);
if ((gethostname(hostname, sizeof(hostname))))
if (!LocalHostName)
exit(1);
if (LocalHostName)
{
printf("Your hostname appears to be [%s]\n", LocalHostName);
}
#ifndef IPV6
if (LocalHostName)
{
struct hostent *hp;
memset(&LocalHostAddr, 0, sizeof(LocalHostAddr));
if ((hp = gethostbyname(LocalHostName)))
memcpy((void *)&LocalHostAddr.sf_addr, hp->h_addr, sizeof(struct in_addr));
}
else
{
struct hostent *hp;
if ((hp = gethostbyname(hostname)))
memcpy(&MyHostAddr.sf_addr, hp->h_addr, sizeof(struct in_addr));
}
#endif
if (!*nickname)
strlcpy(nickname, username, sizeof nickname);
if (!check_nickname(nickname))
{
fprintf(stderr, "Illegal nickname %s\n", nickname);
fprintf(stderr, "Please restart IRC II with a valid nickname\n");
exit(1);
}
if (ircrc_file == NULL)
ircrc_file = m_sprintf("%s/%s", my_path, IRCRC_NAME);
if (bircrc_file == NULL)
bircrc_file = m_sprintf("%s/%s", my_path, BITCHXRC_NAME);
if ((ptr = getenv("IRCPORT")))
irc_port = my_atol(ptr);
if ((ptr = getenv("IRCSERVER")))
build_server_list(ptr);
if (!server_list_size() || add_servers)
{
if (!read_server_file(ircservers_file) && !server_list_size())
{
#ifdef DEFAULT_SERVER
char *ptr = NULL;
malloc_strcpy(&ptr, DEFAULT_SERVER);
build_server_list(ptr);
new_free(&ptr);
#endif
from_server = -1;
}
}
return (channel);
}
/*
* io() is a ONE TIME THROUGH loop! It simply does ONE check on the
* file descriptors, and if there is nothing waiting, it will time
* out and drop out. It does everything as far as checking for exec,
* dcc, ttys, notify, the whole ball o wax, but it does NOT iterate!
*
* You should usually NOT call io() unless you are specifically waiting
* for something from a file descriptor. It doesn't look like bad things
* will happen if you call this elsewhere, but its long time behavior has
* not been observed. It *does* however, appear to be much more reliable
* then the old irc_io, and i even know how this works. >;-)
*/
extern void set_screens (fd_set *, fd_set *);
void BX_io (const char *what)
{
static int level = 0;
long clock_timeout = 0;
struct timeval my_now,
my_timer,
timeout;
int hold_over, rc;
fd_set rd, wd;
static int old_level = 0;
static const char *caller[51] = { NULL }; /* XXXX */
static int last_warn = 0;
level++;
if (x_debug & DEBUG_WAITS)
{
if (level != old_level)
{
yell("Moving from io level [%d] to level [%d] from [%s]", old_level, level, what);
old_level = level;
}
}
if (level && (level - last_warn == 5))
{
last_warn = level;
yell("io's nesting level is [%d], [%s]<-[%s]<-[%s]<-[%s]<-[%s]<-[%s]", level, what, caller[level-1], caller[level-2], caller[level-3], caller[level-4], caller[level-5]);
if ((level % 30) == 0)
ircpanic("Ahoy there matey! Abandon ship!");
return;
}
else if (level && (last_warn -level == 5))
last_warn -= 5;
caller[level] = what;
get_time(&my_now);
now = my_now.tv_sec;
/* CHECK FOR CPU SAVER MODE */
if (!cpu_saver && get_int_var(CPU_SAVER_AFTER_VAR))
if (now - idle_time > get_int_var(CPU_SAVER_AFTER_VAR) * 60)
cpu_saver_on(0, NULL);
rd = readables;
wd = writables;
FD_ZERO(&wd);
FD_ZERO(&rd);
#ifndef GUI
set_screens(&rd, &wd);
#endif
set_process_bits(&rd);
set_socket_read(&rd, &wd);
set_nslookupfd(&rd);
#ifdef GUI
gui_setfd(&rd);
#endif
clock_timeout = 60 - (my_now.tv_sec % 60);
if (cpu_saver && get_int_var(CPU_SAVER_EVERY_VAR))
clock_timeout += (get_int_var(CPU_SAVER_EVERY_VAR) - 1) * 60;
my_timer.tv_sec = my_now.tv_sec + clock_timeout;
my_timer.tv_usec = 0;
set_server_bits(&rd, &wd, &my_timer);
tclTimerTimeout(&my_timer);
TimerTimeout(&my_timer);
if ((hold_over = unhold_windows()) || time_cmp(&my_timer, &my_now) < 0)
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
}
else
{
timeout.tv_sec = my_timer.tv_sec - my_now.tv_sec;
timeout.tv_usec = my_timer.tv_usec - my_now.tv_usec;
if (timeout.tv_usec < 0)
{
timeout.tv_sec--;
timeout.tv_usec += 1000000;
}
}
/* GO AHEAD AND WAIT FOR SOME DATA TO COME IN */
switch ((rc = new_select(&rd, &wd, &timeout)))
{
case 0:
do_idle_server();
break;
case -1:
{
/* if we just got a sigint */
if (cntl_c_hit)
{
edit_char('\003');
/* cntl_c_hit = 0; */
}
else if (errno != EINTR)
yell("Select failed with [%s]", strerror(errno));
break;
}
/* we got something on one of the descriptors */
default:
{
cntl_c_hit = 0;
get_time(&my_now);
now = my_now.tv_sec;
make_window_current(NULL);
do_server(&rd, &wd);
do_processes(&rd);
do_screens(&rd);
#ifdef WANT_CDCC
dcc_sendfrom_queue();
#endif
dcc_check_idle();
print_nslookup(&rd);
scan_sockets(&rd, &wd);
#ifdef GUI
scan_gui(&rd);
#endif
break;
}
}
get_time(&my_now);
now = my_now.tv_sec;
ExecuteTimers();
#ifdef WANT_TCL
check_utimers();
#endif
send_from_server_queue();
get_child_exit(-1);
if (!hold_over)
cursor_to_input();
#ifdef WANT_LLOOK
if (get_int_var(LLOOK_VAR) && from_server > -1 && !get_server_linklook(from_server))
{
if (now - get_server_linklook_time(from_server) > get_int_var(LLOOK_DELAY_VAR))
{
set_server_linklook(from_server, get_server_linklook(from_server)+1);
my_send_to_server(from_server, "LINKS");
set_server_linklook_time(from_server, now);
}
}
#endif
if (update_clock(RESET_TIME))
{
do_notify();
#ifdef WANT_TCL
check_timers();
#endif
clean_whowas_chan_list();
clean_whowas_list();
clean_flood_list();
clean_split_server_list(CHAN_SPLIT, 1800);
#ifdef WANT_LLOOK
clean_split_server_list(LLOOK_SPLIT, 3600);
#endif
if (get_int_var(CLOCK_VAR) || check_mail_status())
{
update_all_status(current_window, NULL, 0);
cursor_to_input();
}
check_server_connect(from_server);
}
/* (set in term.c) -- we should redraw the screen here */
if (need_redraw)
refresh_screen(0, NULL);
FromUserHost = empty_string;
alloca(0);
caller[level] = NULL;
level--;
return;
}
char pidfile[80];
void check_pid(void)
{
char *p = NULL;
FILE *t;
if (!do_check_pid)
return;
p = expand_twiddle(DEFAULT_CTOOLZ_DIR);
snprintf(pidfile, sizeof pidfile, "%s/pid.%s", p, nickname);
if ((t = fopen(pidfile, "r")))
{
char buffer[80];
int i;
fgets(buffer, 10, t);
i = atol(buffer);
kill(i, SIGCHLD);
if (errno != ESRCH)
{
fprintf(stderr, "Bitchx already running as %s\n", nickname);
exit(1);
}
}
}
extern int save_ipc;
static void remove_pid(void)
{
#ifdef WANT_DETACH
char *p;
FILE *t;
if (!do_check_pid)
return;
p = expand_twiddle(DEFAULT_CTOOLZ_DIR);
snprintf(pidfile, sizeof pidfile, "%s/pid.%s", p, nickname);
if ((t = fopen(pidfile, "r")))
{
#if !defined(__EMX__) && !defined(WINNT) && !defined(GUI)
SocketList *s;
fclose(t);
unlink(pidfile);
if (save_ipc != -1 && (s = get_socket(save_ipc)))
{
char buf[500];
snprintf(buf, sizeof buf, s->server, s->port);
unlink(buf);
}
#endif
}
#endif
}
void setup_pid(void)
{
#ifdef WANT_DETACH
pid_t pid;
if (!do_check_pid)
return;
if ((pid = getpid()))
{
FILE *t;
unlink(pidfile);
if ((t = fopen(pidfile, "w")))
{
fprintf(t, "%ld\n", (long)pid);
fclose(t);
}
}
#endif
}
int main(int argc, char *argv[], char *envp[])
{
srand((unsigned)time(NULL));
time(&start_time);
time(&idle_time);
time(&now);
/* We need to zero these early */
FD_ZERO(&readables);
FD_ZERO(&writables);
/* First thing we need to do is initialize
* the global function table with the default
* function pointers.
*/
init_global_functions();
/* Setup OS/2 */
#if defined(__EMX__)
setvbuf(stdout, NULL, _IOLBF, 80);
#endif
/* Setup the GUIs */
#ifdef GUI
gui_startup(argc, argv);
#endif
#ifdef SOCKS
SOCKSinit(argv[0]);
#endif
#ifdef TDEBUG
*cx_file = 0;
cx_line = 0;
*cx_function = 0;
#endif
printf("BitchX - Based on EPIC Software Labs epic ircII (1998).\r\n");
printf("Version (%s) -- Date (%s).\r\n", irc_version, internal_version);
printf("Process [%d]", getpid());
#if !defined(GUI)
if ((isatty(0) && !background) || (!isatty(0) && background))
{
char s[90];
if (ttyname(0))
{
strncpy(s, ttyname(0), 85);
/* printf(" connected to tty [%s]", s);*/
strncpy(attach_ttyname, s, 400);
}
else if (background)
strcpy(attach_ttyname, "tty1");
}
else
dumb_mode = 1;
printf("\n");
#endif
channel = parse_args(argv, argc, envp);
check_pid();
#ifdef WANT_TCL
tcl_interp = Tcl_CreateInterp();
#endif
if (!dumb_mode && term_init(NULL))
_exit(1);
if (background)
{
#ifdef WANT_DETACH
#ifndef PUBLIC_ACCESS
detachcmd(NULL, NULL, NULL, NULL);
#else
exit(0);
#endif
#else
fprintf(stderr, "Try compiling with WANT_DETACH\r\n");
exit(0);
#endif
}
#ifndef WANT_CORE
my_signal(SIGSEGV, coredump, 0);
my_signal(SIGBUS, coredump, 0);
#endif
my_signal(SIGQUIT, SIG_IGN, 0);
#ifdef WANT_DETACH
set_detach_on_hup(NULL, NULL, DEFAULT_DETACH_ON_HUP);
#else
my_signal(SIGHUP, irc_exit_old, 0);
#endif
my_signal(SIGTERM, irc_exit_old, 0);
my_signal(SIGPIPE, SIG_IGN, 0);
my_signal(SIGINT, cntl_c, 0);
my_signal(SIGCHLD, child_reap, 0);
my_signal(SIGALRM, nothing, 0);
my_signal(SIGUSR1, sig_user1, 0);
#ifdef GTK
my_signal(SIGTTOU, SIG_IGN, 0);
#endif
#if 0
my_signal(SIGUSR1, sigusr2, 0);
my_signal(SIGUSR2, sigusr3, 0);
#endif
init_output();
if (!dumb_mode)
{
init_screen();
if (background)
{
use_input = 0;
main_screen->fdin = main_screen->fdout = open("/dev/null", O_RDWR|O_NOCTTY);
new_open(main_screen->fdin);
main_screen->fpout = fdopen(1, "w");
main_screen->fpin = fdopen(0, "r");
}
#ifndef __EMX__
my_signal(SIGCONT, term_cont, 0);
#if !defined(WINNT) && !defined(GTK)
my_signal(SIGWINCH, sig_refresh_screen, 0);
#endif
#endif
}
else
{
if (background)
{
my_signal(SIGHUP, SIG_IGN, 0);
background = 0;
#ifndef WANT_DETACH
term_reset();
fprintf(stderr, "try recompiling the client with WANT_DETACH defined\r\n");
exit(0);
use_input = 0;
#endif
}
create_new_screen();
new_window(main_screen);
}
setup_pid();
init_keys();
init_keys2();
init_variables();
init_dcc_table();
#if defined(THREAD) && defined(WANT_NSLOOKUP)
start_dns();
#endif
if (!dumb_mode)
{
build_status(current_window, NULL, 0);
update_input(UPDATE_ALL);
}
start_identd();
#ifdef WANT_TCL
tcl_init();
#ifdef WANT_TK
Tk_Init(tcl_interp);
#endif
add_tcl_vars();
#endif
#ifdef HAVE_LIBSSL
{
char *entropy = malloc(100);
int i;
for(i=0;i<100;i++)
entropy[i] = (char) getrandom(0, 255);
/* Many systems don't have /dev/random so we seed */
RAND_seed(entropy, 100);
SSLeay_add_ssl_algorithms();
SSL_load_error_strings();
free(entropy);
}
#endif
#ifdef CLOAKED
initsetproctitle(argc, argv, envp);
setproctitle("%s", CLOAKED);
#endif
/* We move from run level 0 to run level 1
* signifying that we have completed the initial
* startup and are now ready to load scripts.
*/
run_level = 1;
display_bitchx(-1);
if (bflag)
load_scripts();
#ifdef GUI
gui_font_init();
#endif
reinit_autoresponse(current_window, NULL, 0);
if (auto_connect)
get_connected(0, -1);
else
display_server_list();
start_memdebug();
set_input(empty_string);
set_input_prompt(current_window, get_string_var(INPUT_PROMPT_VAR), 0);
/* We now move from run level 1 to run level 2
* signifying that we are in normal operation mode.
*/
run_level = 2;
for (;;)
io("main");
#ifdef GUI
gui_exit();
#else
ircpanic("get_line() returned");
#endif
return (-((int)0xdead));
}