#ident "@(#)irc.c 1.23" /* * Original from ircII: a new irc client. I like it. I hope you will too! * Written By Michael Sandrof * Copyright(c) 1990 * * Modified by Rex Feany for Xaric * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #ifdef USING_CURSES # include #endif #include "irc.h" #include "log.h" #include "dcc.h" #include "misc.h" #include "hook.h" #include "keys.h" #include "exec.h" #include "vars.h" #include "names.h" #include "build.h" #include "debug.h" #include "newio.h" #include "flood.h" #include "input.h" #include "timer.h" #include "output.h" #include "status.h" #include "screen.h" #include "server.h" #include "window.h" #include "notify.h" #include "whowas.h" #include "ircaux.h" #include "history.h" #include "numbers.h" #include "ircterm.h" #include "commands.h" #include "tcommand.h" /** Global Variables **/ /* These are all just "handy" variables. */ char zero_str[] = "0", one_str[] = "1", space_str[] = " ", on_str[] = "ON", off_str[] = "OFF", empty_str[] = ""; char *invite_channel = NULL, /* last channel of an INVITE */ *ircrc_file = NULL, /* full path .ircrc file */ *my_path = NULL, /* path to users home dir */ *ircservers_file = NULL, /* name of server file */ nickname[NICKNAME_LEN + 1], /* users nickname */ realname[REALNAME_LEN + 1], /* real name of user */ username[NAME_LEN + 1], /* usernameof user */ *send_umode = NULL, /* sent umode */ *who_name = NULL, /* extra /who switch info */ *who_file = NULL, /* extra /who switch info */ *who_server = NULL, /* extra /who switch info */ *who_host = NULL, /* extra /who switch info */ *who_nick = NULL, /* extra /who switch info */ *who_real = NULL, /* extra /who switch info */ *cut_buffer = NULL, /* global cut_buffer */ *prog_name = NULL, /* argv[0] */ *line_thing = NULL, /* show_numeric_str, say( ), put at begin of line */ 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 */ def_hostname[NAME_LEN+1], /* gethostname() return */ *local_host_name = NULL; /* host the user would like to use (-H, /hostname) */ int irc_port = IRC_PORT, /* port of ircd */ strip_ansi_in_echo, /* used in the send_text() */ current_on_hook = -1, current_numeric, /* this is negative of the current numeric! */ key_pressed = 0, waiting_out = 0, /* used by /WAIT command */ waiting_in = 0, /* used by /WAIT command */ who_mask = 0, /* keeps track of which /who switchs are set */ away_set = 0, /* set if there is an away message anywhere */ #ifdef HAVE_SSL do_use_ssl = 0, #endif need_redraw = 0; /* set whenever the terminal is reset */ static int load_ircrc = 1, /* load ircrc file? */ load_ircservers = 1; /* load servers file? */ struct in_addr MyHostAddr; /* The local machine address */ struct in_addr LocalHostAddr; time_t idle_time = 0, start_time; /* Display the startup message */ static void startup_message(void) { int old = strip_ansi_in_echo; strip_ansi_in_echo = 0; /* black magic. leave me alone. */ put_it(empty_str); put_fmt_str("%g***%n", NULL); put_fmt_str("%g***%C $0-", "%s", PACKAGE_STRING); put_fmt_str("%g***%n", NULL); put_fmt_str("%g***%n Xaric is free software, covered by the GNU General Public License,", NULL); put_fmt_str("%g***%n and you are welcome to change it and/or distribute copies of it", NULL); put_fmt_str("%g***%n under certain conditions.", NULL); put_fmt_str("%g***%n Type \"/help copying\" to see the conditions.", NULL); put_fmt_str("%g***%n", NULL); put_fmt_str("%g***%n There is absolutely no warranty for Xaric.", NULL); put_fmt_str("%g***%n Type \"/help warranty\" for details.", NULL); put_fmt_str("%g***%n", NULL); strip_ansi_in_echo = old; } static void usage(void) __attribute__ ((noreturn)); /* Print out the usage message, and die */ static void usage(void) { static char help[] = "Usage: %s [OPTION]... [nick [server list]...]\n" " -n do not load your " IRCRC_NAME " file.\n" " -S do not load your " IRCSERVERS_NAME " file.\n" " -h this help text.\n" " -v display xaric version and exit.\n" " -f your terminal users flow control (^s/^q), so xaric shouldn't.\n" " -s Use SSL.\n" " -F your terminal does not use flow control.\n" " -H uses the virtual hostname if possible.\n" " -d set debug options (see documentation).\n" " -p set default port to use (normally 6667).\n" " -l loads file instead of " IRCRC_NAME ".\n" " -r loads file as list of servers, instead of " IRCSERVERS_NAME ".\n\n" "Xaric uses the values in IRCSERVER, IRCPORT, IRCRC, IRCNICK, IRCNAME\n" "IRCHOST, IRCUMODE, IRCSERVERS\n\n" "Command line arguments override environment variables.\n\n" "Report bugs to bugs@xaric.org\n"; fprintf(stderr, help, prog_name); exit(EXIT_FAILURE); } /* Parse command line arguments. */ static void parse_args(int argc, char *argv[]) { static const char optstr[] = "vhH:p:fFl:r:nsSd:"; while (1) { int c = getopt(argc, argv, optstr); if (-1 == c) break; switch (c) { case 'v': /* print version */ puts(PACKAGE_STRING); putchar('\n'); exit(0); case 'H': /* Set local hostname, for vhost stuff */ malloc_strcpy(&local_host_name, optarg); break; case 'p': /* Default port to use */ irc_port = my_atol(optarg); break; case 'f': /* Use flow control */ term_set_flow_control(ON); break; case 'F': /* dont use flow control */ term_set_flow_control(OFF); break; case 'l': /* Load some file instead of ~/.ircrc */ malloc_strcpy(&ircrc_file, optarg); break; case 'r': /* Load list of servers from this file */ malloc_strcpy(&ircservers_file, optarg); case 'n': /* do not load ircrc */ load_ircrc = 0; break; case 'S': /* do not load .ircservers file */ load_ircservers = 0; break; case 'd': /* set server debug */ #ifdef XARIC_DEBUG if (xd_parse(optarg)) { fprintf(stderr, "Bad arguments to -d\n"); exit(1); } #else fprintf(stderr, "Debug fluf not compiled in!\n"); #endif /* XARIC_DEBUG */ break; case 's': /* use ssl */ do_use_ssl = 1; break; case '?': /* unknown option */ case ':': /* missing argument */ case 'h': /* help */ default: usage(); /* does not return */ } } /* first non-option argument is a nickname */ if (optind < argc) strmcpy(nickname, argv[optind++], NICKNAME_LEN); /* the rest are servers */ while (optind < argc) build_server_list(argv[optind++]); } /* sniff out user information from passwd file */ static void get_user_info(void) { struct passwd *entry; char *ptr; if (!(entry = getpwuid(getuid()))) return; if (!*realname && entry->pw_gecos && *(entry->pw_gecos)) { #ifdef GECOS_DELIMITER if ((ptr = index(entry->pw_gecos, GECOS_DELIMITER))) *ptr = (char) 0; #endif if ((ptr = strchr(entry->pw_gecos, '&')) == NULL) strmcpy(realname, entry->pw_gecos, REALNAME_LEN); else { int len = ptr - entry->pw_gecos; if (len < REALNAME_LEN && *(entry->pw_name)) { char *q = realname + len; strmcpy(realname, entry->pw_gecos, len); strmcat(realname, entry->pw_name, REALNAME_LEN); strmcat(realname, ptr + 1, REALNAME_LEN); if (islower(*q) && (q == realname || isspace(*(q - 1)))) *q = toupper(*q); } else strmcpy(realname, entry->pw_gecos, REALNAME_LEN); } } if (entry->pw_name && *(entry->pw_name) && !*username) strmcpy(username, entry->pw_name, NAME_LEN); if (entry->pw_dir && *(entry->pw_dir)) malloc_strcpy(&my_path, entry->pw_dir); } /* Load data in from environment variables. */ /* We expect this to initilize most globals. */ static void load_xaric_environment(void) { char *ptr; if ((ptr = getenv("IRCNICK"))) strmcpy(nickname, ptr, NICKNAME_LEN); if ((ptr = getenv("IRCUMODE"))) malloc_strcpy(&send_umode, ptr); if ((ptr = getenv("HOME"))) malloc_strcpy(&my_path, ptr); if ((ptr = getenv("IRCRC"))) malloc_strcpy(&ircrc_file, ptr); if ((ptr = getenv("IRCSERVERS"))) malloc_strcpy(&ircservers_file, ptr); if ((ptr = getenv("IRCNAME")) || (ptr = getenv("NAME"))) strmcpy(realname, ptr, REALNAME_LEN); if ((ptr = getenv("IRCUSER")) || (ptr = getenv("USER"))) strmcpy(username, ptr, NAME_LEN); if ((ptr = getenv("IRCHOST"))) local_host_name = m_strdup(ptr); if ((ptr = getenv("IRCPORT"))) irc_port = my_atol(ptr); if ((ptr = getenv("IRCSERVER"))) build_server_list(ptr); } /* Make sure all the global variables are initilized. */ static void load_xaric_finish(void) { if (!nickname || !*nickname) strmcpy(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 (!*realname) strmcpy(realname, "*Unknown*", REALNAME_LEN); if (!*username) strmcpy(username, "*Unknown*", NAME_LEN); if (!my_path || !*my_path) malloc_strcpy(&my_path, "/"); if (!ircservers_file) malloc_strcpy(&ircservers_file, IRCSERVERS_NAME); if (!ircrc_file) malloc_strcpy(&ircrc_file, IRCRC_NAME); } /* load irc servers file */ static void load_xaric_servers(void) { /* load server files here */ if (load_ircservers) read_server_file(ircservers_file); #ifdef DEFAULT_SERVER if (server_list_size() == 0) { char *ptr = m_strdup(DEFAULT_SERVER); build_server_list(ptr); new_free(&ptr); /* XXX build_server_list should take const */ } #endif if (server_list_size() == 0) ircpanic("I have know no servers!"); } /* irc_exit: cleans up and leaves */ void irc_exit(char *reason, char *formated) { int old_window_display = window_display; do_hook(EXIT_LIST, "%s", reason); close_server(-1, reason); put_it("%s", formated ? formated : reason); logger(curr_scr_win, NULL, 0); if (get_int_var(MSGLOG_VAR)) log_toggle(0, NULL); clean_up_processes(); cursor_to_input(); /* Needed so that ircII doesn't gobble the last line of the kill. */ term_cr(); term_clear_to_eol(); term_reset(); /* Debugging sanity. */ window_display = 0; set_lastlog_size(curr_scr_win, NULL, 0); set_history_size(curr_scr_win, NULL, 0); remove_channel(NULL, 0); window_display = old_window_display; clear_bindings(); clear_sets(); fprintf(stdout, "\r"); fflush(stdout); exit(0); } #ifdef HAVE_SSL static void init_ssl(void) { 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 /* HAVE_SSL */ /* initilize global variables, parse command line, etc */ static void xaric_init(int argc, char *argv[]) { get_user_info(); #ifdef HAVE_SSL init_ssl(); #endif load_xaric_environment(); parse_args(argc, argv); init_hostname(); load_xaric_finish(); load_xaric_servers(); if (init_screen()) { fprintf(stderr, "%s: Woops! Couldn't init the terminal!\n", prog_name); exit(1); } signals_init(); init_variables(); init_keys_1(); init_commands(); build_status(curr_scr_win, NULL, 0); update_input(UPDATE_ALL); startup_message(); } /* new irc_io modularized stuff */ /* * GetLineStruct is what is "under" your current input line, and the function * we're supposed to call when you press return. This is different from * AddWaitPrompt which does functionally the same thing but doesnt cause * recursive calls to io. */ struct GetLineStruct { int done; void (*func) (char, char *); char *saved_input; char *saved_prompt; int recursive_call; struct GetLineStruct *prev; struct GetLineStruct *next; }; typedef struct GetLineStruct GetLine; GetLine *GetLineStack = NULL; /* when you press return, you call this. */ extern void get_line_return(char unused, char *not_used) { GetLine *stuff; /* get the last item on the stack */ if ((stuff = GetLineStack) == NULL) return; /* * If we're NOT the main() call, then undo all that we messed up coming in. If stuff->done gets set to 1 when recursive_call is * zero, then something is VERY wrong. We can set stuff->prev->next to null because the call to get_line() holds a pointer to * stuff, so when it unrecurses, it will free it. */ if (stuff->func) { not_used = NULL; (stuff->func) (unused, not_used); } if (stuff->recursive_call) { stuff->done = 1; set_input(stuff->saved_input); set_input_prompt(curr_scr_win, stuff->saved_prompt, 0); new_free(&(stuff->saved_input)); new_free(&(stuff->saved_prompt)); stuff->next->prev = NULL; GetLineStack = stuff->next; } update_input(UPDATE_ALL); /* We cant delete stuff here becuase the get_line function still needs to look at stuff->done. So we let it delete the items off * the list. But we removed it from the list, so we wont accidentally use it later. */ return; } /* This is a wrapper for io(). Only two functions at any time are allowed * to call it, and main() is one of those two. When you call it, you have * the option to change the input prompt and the input buffer. You also * give it a function to call when it gets a return. Only main() is * allowed to call it with an new_input of -1, which tells it that it is * at the lowest level of parsing, by which i mean that noone is waiting * for anything, since there is no recursion going on. */ void get_line(char *prompt, int new_input, void (*func) (char, char *)) { GetLine *stuff; if (GetLineStack && new_input == -1) ircpanic("Illegal call to get_line\n"); /* initialize the new item. */ stuff = (GetLine *) new_malloc(sizeof(GetLine)); stuff->done = 0; stuff->func = func; stuff->recursive_call = (new_input == -1) ? 0 : 1; stuff->saved_input = NULL; stuff->saved_prompt = NULL; stuff->prev = NULL; stuff->next = NULL; malloc_strcpy(&(stuff->saved_input), get_input()); malloc_strcpy(&(stuff->saved_prompt), get_input_prompt()); /* put it on the stack */ if (GetLineStack) { stuff->next = GetLineStack; GetLineStack->prev = stuff; } GetLineStack = stuff; /* if its a global call, get the input prompt */ if (new_input == -1) set_input_prompt(curr_scr_win, get_string_var(INPUT_PROMPT_VAR), 0); else set_input_prompt(curr_scr_win, prompt, 0); set_input(empty_str); /* ok. we call io() until the user presses return, ending the input line. get_line_return will then set get_line_done to one, * and we will stop getting characters and drop out. get_line_done NEVER sets this to one if we are in our call from main(). * NEVER. */ while (!stuff->done) io("get line"); if (new_input == -1) ircpanic("get_line: input == -1 is illegal value"); /* By the time we get here, stuff->done has been set to 1, which means that get_line_return has already freed the interesting * items in stuff and removed it from the list. Noone but us has a pointer to it, so we free it here. */ new_free(&stuff->saved_input); new_free(&stuff->saved_prompt); new_free((char **) &stuff); } /* This simply waits for a key to be pressed before it unrecurses. * It doesnt do anyting in particular with that key (it will go to * the input buffer, actually) */ char get_a_char(void) { key_pressed = 0; while (!key_pressed) io("get a char"); update_input(UPDATE_ALL); return key_pressed; } /* * 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 doesnt 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 io(const char *what) { static int first_time = 1, level = 0; static struct timeval cursor_timeout, clock_timeout, right_away, timer, *timeptr = NULL; int hold_over; fd_set rd, wd; static int old_level = 0; Screen *screen, *old_current_screen = current_screen; static const char *caller[51] = { NULL }; /* XXXX */ static int last_warn = 0; time_t now = time(NULL); level++; if (level != old_level) { DEBUG(XD_COMM, 5, "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]); if (level % 50 == 0) ircpanic("Ahoy there matey! Abandon ship!"); return; } else if (level && (last_warn - level == 5)) last_warn -= 5; caller[level] = what; /* first time we run this function, set up the timeouts */ if (first_time) { first_time = 0; /* time before cursor jumps from display area to input line */ cursor_timeout.tv_usec = 0L; cursor_timeout.tv_sec = 1L; /* * time delay for updating of internal clock * * Instead of looking every 15 seconds and seeing if * the clock has changed, we now figure out how much * time there is to the next clock change and then wait * until then. There is a small performance penalty * in actually calculating when the next minute will tick, * but that will be offset by the fact that we will only * call select() once a minute instead of 4 times. */ clock_timeout.tv_usec = 0L; right_away.tv_usec = 0L; right_away.tv_sec = 0L; timer.tv_usec = 0L; } /* SET UP TIMEOUTS USED IN SELECTING */ /* clock_timeout.tv_sec = time_to_next_minute(); */ FD_ZERO(&wd); FD_ZERO(&rd); set_screens(&rd, &wd); set_dcc_bits(&rd, &wd); set_server_bits(&rd, &wd); set_process_bits(&rd); clock_timeout.tv_sec = (60 - now % 60); /* if the time changes, now - idle_time can be negative, and cause all kinds of problems */ if (now > idle_time) clock_timeout.tv_sec += now - idle_time; if (!timeptr) timeptr = &clock_timeout; timer.tv_sec = TimerTimeout(); if (timer.tv_sec <= timeptr->tv_sec) timeptr = &timer; #if 0 if ((hold_over = unhold_windows()) != 0) timeptr = &right_away; #else hold_over = 0; #endif /* go ahead and wait for some data to come in */ switch (new_select(&rd, &wd, timeptr)) { case 0: break; case -1: { /* if we just got a sigint */ if (cntl_c_hit) { key_pressed = 3; edit_char('\003'); cntl_c_hit = 0; } else if (errno != EINTR && errno > 0) yell("Select failed with [%s]", strerror(errno)); break; } /* we got something on one of the descriptors */ default: { set_current_screen(last_input_screen); dcc_check(&rd, &wd); do_server(&rd, &wd); do_processes(&rd); do_screens(&rd); dcc_check_idle(); set_current_screen(old_current_screen); break; } } ExecuteTimers(); while (got_sigchild) { check_wait_status(-1); got_sigchild--; } if (!hold_over) cursor_to_input(); timeptr = &clock_timeout; for (screen = screen_list; screen; screen = screen->next) if (screen->alive && is_cursor_in_display(screen)) timeptr = &cursor_timeout; if (get_int_var(LLOOK_VAR) && from_server > -1 && !server_list[from_server].link_look) { if (time(NULL) - server_list[from_server].link_look_time > get_int_var(LLOOK_DELAY_VAR)) { server_list[from_server].link_look++; send_to_server(SERVER(from_server), "LINKS"); server_list[from_server].link_look_time = time(NULL); } } if (update_clock(0)) { do_notify(); clean_whowas_chan_list(); clean_whowas_list(); clean_flood_list(); if (get_int_var(CLOCK_VAR)) { update_all_status(curr_scr_win, 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); caller[level] = NULL; level--; return; } /* This is the main xaric "loop" */ static void xaric_main(void) { if (load_ircrc) load_scripts(); get_connected(0); set_input(empty_str); get_line(NULL, -1, send_line); ircpanic("get_line() returned"); } int main(int argc, char *argv[], char *envp[]) { int retval = EXIT_FAILURE; srand((unsigned) time(NULL)); time(&start_time); time(&idle_time); prog_name = argv[0]; if (!isatty(0)) { fprintf(stderr, "Woops I need a tty!\n"); exit(1); } xaric_init(argc, argv); xaric_main(); return retval; }