diff options
Diffstat (limited to 'src/common')
70 files changed, 31320 insertions, 0 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am new file mode 100644 index 00000000..6a35da91 --- /dev/null +++ b/src/common/Makefile.am @@ -0,0 +1,61 @@ +## Process this file with automake to produce Makefile.in + +noinst_LIBRARIES = libxchatcommon.a + +INCLUDES = $(COMMON_CFLAGS) + +EXTRA_DIST = \ + cfgfiles.h \ + chanopt.h \ + ctcp.h \ + dcc.h \ + fe.h \ + history.h \ + identd.c \ + ignore.h \ + inbound.h \ + inet.h \ + make-te.c \ + modes.h \ + msproxy.h \ + network.h \ + notify.h \ + outbound.h \ + plugin.h \ + plugin-timer.h \ + proto-irc.h \ + server.h \ + servlist.h \ + ssl.h \ + ssl.c \ + text.h \ + textenums.h \ + textevents.h \ + textevents.in \ + tree.h \ + url.h \ + userlist.h \ + util.h \ + xchat.h \ + xchatc.h \ + xchat-plugin.h + +if USE_OPENSSL +ssl_c = ssl.c +endif + +if USE_DBUS +dbusdir = dbus +libxchatcommon_a_LIBADD = \ + $(top_builddir)/src/common/dbus/dbus-*.$(OBJEXT) +endif +SUBDIRS = $(dbusdir) . + +libxchatcommon_a_SOURCES = cfgfiles.c chanopt.c ctcp.c dcc.c history.c ignore.c \ + inbound.c modes.c msproxy.c network.c notify.c outbound.c \ + plugin.c plugin-timer.c proto-irc.c server.c servlist.c $(ssl_c) \ + text.c tree.c url.c userlist.c util.c xchat.c +libxchatcommon_a_CFLAGS = $(LIBPROXY_CFLAGS) + +textevents: make-te + ./make-te < textevents.in > textevents.h 2> textenums.h diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c new file mode 100644 index 00000000..8bb2a61b --- /dev/null +++ b/src/common/cfgfiles.c @@ -0,0 +1,1105 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "xchat.h" +#include "cfgfiles.h" +#include "util.h" +#include "fe.h" +#include "text.h" +#include "xchatc.h" + +#ifdef WIN32 +#define XCHAT_DIR "X-Chat 2" +#else +#define XCHAT_DIR ".xchat2" +#endif +#define DEF_FONT "Monospace 9" + +void +list_addentry (GSList ** list, char *cmd, char *name) +{ + struct popup *pop; + int cmd_len = 1, name_len; + + /* remove <2.8.0 stuff */ + if (!strcmp (cmd, "away") && !strcmp (name, "BACK")) + return; + + if (cmd) + cmd_len = strlen (cmd) + 1; + name_len = strlen (name) + 1; + + pop = malloc (sizeof (struct popup) + cmd_len + name_len); + pop->name = (char *) pop + sizeof (struct popup); + pop->cmd = pop->name + name_len; + + memcpy (pop->name, name, name_len); + if (cmd) + memcpy (pop->cmd, cmd, cmd_len); + else + pop->cmd[0] = 0; + + *list = g_slist_append (*list, pop); +} + +/* read it in from a buffer to our linked list */ + +static void +list_load_from_data (GSList ** list, char *ibuf, int size) +{ + char cmd[384]; + char name[128]; + char *buf; + int pnt = 0; + + cmd[0] = 0; + name[0] = 0; + + while (buf_get_line (ibuf, &buf, &pnt, size)) + { + if (*buf != '#') + { + if (!strncasecmp (buf, "NAME ", 5)) + { + safe_strcpy (name, buf + 5, sizeof (name)); + } + else if (!strncasecmp (buf, "CMD ", 4)) + { + safe_strcpy (cmd, buf + 4, sizeof (cmd)); + if (*name) + { + list_addentry (list, cmd, name); + cmd[0] = 0; + name[0] = 0; + } + } + } + } +} + +void +list_loadconf (char *file, GSList ** list, char *defaultconf) +{ + char filebuf[256]; + char *ibuf; + int fh; + struct stat st; + + snprintf (filebuf, sizeof (filebuf), "%s/%s", get_xdir_fs (), file); + fh = open (filebuf, O_RDONLY | OFLAGS); + if (fh == -1) + { + if (defaultconf) + list_load_from_data (list, defaultconf, strlen (defaultconf)); + return; + } + if (fstat (fh, &st) != 0) + { + perror ("fstat"); + abort (); + } + + ibuf = malloc (st.st_size); + read (fh, ibuf, st.st_size); + close (fh); + + list_load_from_data (list, ibuf, st.st_size); + + free (ibuf); +} + +void +list_free (GSList ** list) +{ + void *data; + while (*list) + { + data = (void *) (*list)->data; + free (data); + *list = g_slist_remove (*list, data); + } +} + +int +list_delentry (GSList ** list, char *name) +{ + struct popup *pop; + GSList *alist = *list; + + while (alist) + { + pop = (struct popup *) alist->data; + if (!strcasecmp (name, pop->name)) + { + *list = g_slist_remove (*list, pop); + free (pop); + return 1; + } + alist = alist->next; + } + return 0; +} + +char * +cfg_get_str (char *cfg, char *var, char *dest, int dest_len) +{ + while (1) + { + if (!strncasecmp (var, cfg, strlen (var))) + { + char *value, t; + cfg += strlen (var); + while (*cfg == ' ') + cfg++; + if (*cfg == '=') + cfg++; + while (*cfg == ' ') + cfg++; + /*while (*cfg == ' ' || *cfg == '=') + cfg++; */ + value = cfg; + while (*cfg != 0 && *cfg != '\n') + cfg++; + t = *cfg; + *cfg = 0; + safe_strcpy (dest, value, dest_len); + *cfg = t; + return cfg; + } + while (*cfg != 0 && *cfg != '\n') + cfg++; + if (*cfg == 0) + return 0; + cfg++; + if (*cfg == 0) + return 0; + } +} + +static int +cfg_put_str (int fh, char *var, char *value) +{ + char buf[512]; + int len; + + snprintf (buf, sizeof buf, "%s = %s\n", var, value); + len = strlen (buf); + return (write (fh, buf, len) == len); +} + +int +cfg_put_color (int fh, int r, int g, int b, char *var) +{ + char buf[400]; + int len; + + snprintf (buf, sizeof buf, "%s = %04x %04x %04x\n", var, r, g, b); + len = strlen (buf); + return (write (fh, buf, len) == len); +} + +int +cfg_put_int (int fh, int value, char *var) +{ + char buf[400]; + int len; + + if (value == -1) + value = 1; + + snprintf (buf, sizeof buf, "%s = %d\n", var, value); + len = strlen (buf); + return (write (fh, buf, len) == len); +} + +int +cfg_get_color (char *cfg, char *var, int *r, int *g, int *b) +{ + char str[128]; + + if (!cfg_get_str (cfg, var, str, sizeof (str))) + return 0; + + sscanf (str, "%04x %04x %04x", r, g, b); + return 1; +} + +int +cfg_get_int_with_result (char *cfg, char *var, int *result) +{ + char str[128]; + + if (!cfg_get_str (cfg, var, str, sizeof (str))) + { + *result = 0; + return 0; + } + + *result = 1; + return atoi (str); +} + +int +cfg_get_int (char *cfg, char *var) +{ + char str[128]; + + if (!cfg_get_str (cfg, var, str, sizeof (str))) + return 0; + + return atoi (str); +} + +char *xdir_fs = NULL; /* file system encoding */ +char *xdir_utf = NULL; /* utf-8 encoding */ + +#ifdef WIN32 + +#include <windows.h> + +static gboolean +get_reg_str (const char *sub, const char *name, char *out, DWORD len) +{ + HKEY hKey; + DWORD t; + + if (RegOpenKeyEx (HKEY_CURRENT_USER, sub, 0, KEY_READ, &hKey) == + ERROR_SUCCESS) + { + if (RegQueryValueEx (hKey, name, NULL, &t, out, &len) != ERROR_SUCCESS || + t != REG_SZ) + { + RegCloseKey (hKey); + return FALSE; + } + out[len-1] = 0; + RegCloseKey (hKey); + return TRUE; + } + + return FALSE; +} + +char * +get_xdir_fs (void) +{ + if (!xdir_fs) + { + char out[256]; + + if (!get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\" + "Explorer\\Shell Folders", "AppData", out, sizeof (out))) + return "./config"; + xdir_fs = g_strdup_printf ("%s\\" XCHAT_DIR, out); + } + return xdir_fs; +} + +#else + +char * +get_xdir_fs (void) +{ + if (!xdir_fs) + xdir_fs = g_strdup_printf ("%s/" XCHAT_DIR, g_get_home_dir ()); + + return xdir_fs; +} + +#endif /* !WIN32 */ + +char * +get_xdir_utf8 (void) +{ + if (!xdir_utf) /* never free this, keep it for program life time */ + xdir_utf = xchat_filename_to_utf8 (get_xdir_fs (), -1, 0, 0, 0); + + return xdir_utf; +} + +static void +check_prefs_dir (void) +{ + char *dir = get_xdir_fs (); + if (access (dir, F_OK) != 0) + { +#ifdef WIN32 + if (mkdir (dir) != 0) +#else + if (mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR) != 0) +#endif + fe_message (_("Cannot create ~/.xchat2"), FE_MSG_ERROR); + } +} + +static char * +default_file (void) +{ + static char *dfile = 0; + + if (!dfile) + { + dfile = malloc (strlen (get_xdir_fs ()) + 12); + sprintf (dfile, "%s/xchat.conf", get_xdir_fs ()); + } + return dfile; +} + +/* Keep these sorted!! */ + +const struct prefs vars[] = { + {"auto_save", P_OFFINT (autosave), TYPE_BOOL}, + {"auto_save_url", P_OFFINT (autosave_url), TYPE_BOOL}, + + {"away_auto_unmark", P_OFFINT (auto_unmark_away), TYPE_BOOL}, + {"away_reason", P_OFFSET (awayreason), TYPE_STR}, + {"away_show_message", P_OFFINT (show_away_message), TYPE_BOOL}, + {"away_show_once", P_OFFINT (show_away_once), TYPE_BOOL}, + {"away_size_max", P_OFFINT (away_size_max), TYPE_INT}, + {"away_timeout", P_OFFINT (away_timeout), TYPE_INT}, + {"away_track", P_OFFINT (away_track), TYPE_BOOL}, + + {"completion_amount", P_OFFINT (completion_amount), TYPE_INT}, + {"completion_auto", P_OFFINT (nickcompletion), TYPE_BOOL}, + {"completion_sort", P_OFFINT (completion_sort), TYPE_INT}, + {"completion_suffix", P_OFFSET (nick_suffix), TYPE_STR}, + + {"dcc_auto_chat", P_OFFINT (autodccchat), TYPE_INT}, + {"dcc_auto_resume", P_OFFINT (autoresume), TYPE_BOOL}, + {"dcc_auto_send", P_OFFINT (autodccsend), TYPE_INT}, + {"dcc_blocksize", P_OFFINT (dcc_blocksize), TYPE_INT}, + {"dcc_completed_dir", P_OFFSET (dcc_completed_dir), TYPE_STR}, + {"dcc_dir", P_OFFSET (dccdir), TYPE_STR}, + {"dcc_fast_send", P_OFFINT (fastdccsend), TYPE_BOOL}, + {"dcc_global_max_get_cps", P_OFFINT (dcc_global_max_get_cps), TYPE_INT}, + {"dcc_global_max_send_cps", P_OFFINT (dcc_global_max_send_cps), TYPE_INT}, + {"dcc_ip", P_OFFSET (dcc_ip_str), TYPE_STR}, + {"dcc_ip_from_server", P_OFFINT (ip_from_server), TYPE_BOOL}, + {"dcc_max_get_cps", P_OFFINT (dcc_max_get_cps), TYPE_INT}, + {"dcc_max_send_cps", P_OFFINT (dcc_max_send_cps), TYPE_INT}, + {"dcc_permissions", P_OFFINT (dccpermissions), TYPE_INT}, + {"dcc_port_first", P_OFFINT (first_dcc_send_port), TYPE_INT}, + {"dcc_port_last", P_OFFINT (last_dcc_send_port), TYPE_INT}, + {"dcc_remove", P_OFFINT (dcc_remove), TYPE_BOOL}, + {"dcc_save_nick", P_OFFINT (dccwithnick), TYPE_BOOL}, + {"dcc_send_fillspaces", P_OFFINT (dcc_send_fillspaces), TYPE_BOOL}, + {"dcc_stall_timeout", P_OFFINT (dccstalltimeout), TYPE_INT}, + {"dcc_timeout", P_OFFINT (dcctimeout), TYPE_INT}, + + {"dnsprogram", P_OFFSET (dnsprogram), TYPE_STR}, + + {"flood_ctcp_num", P_OFFINT (ctcp_number_limit), TYPE_INT}, + {"flood_ctcp_time", P_OFFINT (ctcp_time_limit), TYPE_INT}, + {"flood_msg_num", P_OFFINT (msg_number_limit), TYPE_INT}, + {"flood_msg_time", P_OFFINT (msg_time_limit), TYPE_INT}, + + {"gui_auto_open_chat", P_OFFINT (autoopendccchatwindow), TYPE_BOOL}, + {"gui_auto_open_dialog", P_OFFINT (autodialog), TYPE_BOOL}, + {"gui_auto_open_recv", P_OFFINT (autoopendccrecvwindow), TYPE_BOOL}, + {"gui_auto_open_send", P_OFFINT (autoopendccsendwindow), TYPE_BOOL}, + {"gui_dialog_height", P_OFFINT (dialog_height), TYPE_INT}, + {"gui_dialog_left", P_OFFINT (dialog_left), TYPE_INT}, + {"gui_dialog_top", P_OFFINT (dialog_top), TYPE_INT}, + {"gui_dialog_width", P_OFFINT (dialog_width), TYPE_INT}, + {"gui_hide_menu", P_OFFINT (hidemenu), TYPE_BOOL}, + {"gui_input_spell", P_OFFINT (gui_input_spell), TYPE_BOOL}, + {"gui_input_style", P_OFFINT (style_inputbox), TYPE_BOOL}, + {"gui_join_dialog", P_OFFINT (gui_join_dialog), TYPE_BOOL}, + {"gui_lagometer", P_OFFINT (lagometer), TYPE_INT}, + {"gui_mode_buttons", P_OFFINT (chanmodebuttons), TYPE_BOOL}, + {"gui_pane_left_size", P_OFFINT (gui_pane_left_size), TYPE_INT}, + {"gui_pane_right_size", P_OFFINT (gui_pane_right_size), TYPE_INT}, + {"gui_quit_dialog", P_OFFINT (gui_quit_dialog), TYPE_BOOL}, + {"gui_slist_fav", P_OFFINT (slist_fav), TYPE_INT}, + {"gui_slist_select", P_OFFINT (slist_select), TYPE_INT}, + {"gui_slist_skip", P_OFFINT (slist_skip), TYPE_BOOL}, + {"gui_throttlemeter", P_OFFINT (throttlemeter), TYPE_INT}, + {"gui_topicbar", P_OFFINT (topicbar), TYPE_BOOL}, + {"gui_tray", P_OFFINT (gui_tray), TYPE_BOOL}, + {"gui_tray_flags", P_OFFINT (gui_tray_flags), TYPE_INT}, + {"gui_tweaks", P_OFFINT (gui_tweaks), TYPE_INT}, + {"gui_ulist_buttons", P_OFFINT (userlistbuttons), TYPE_BOOL}, + {"gui_ulist_doubleclick", P_OFFSET (doubleclickuser), TYPE_STR}, + {"gui_ulist_hide", P_OFFINT (hideuserlist), TYPE_BOOL}, + {"gui_ulist_left", P_OFFINT (_gui_ulist_left), TYPE_BOOL}, /* obsolete */ + {"gui_ulist_pos", P_OFFINT (gui_ulist_pos), TYPE_INT}, + {"gui_ulist_resizable", P_OFFINT (paned_userlist), TYPE_BOOL}, + {"gui_ulist_show_hosts", P_OFFINT(showhostname_in_userlist), TYPE_BOOL}, + {"gui_ulist_sort", P_OFFINT (userlist_sort), TYPE_INT}, + {"gui_ulist_style", P_OFFINT (style_namelistgad), TYPE_BOOL}, + {"gui_url_mod", P_OFFINT (gui_url_mod), TYPE_INT}, + {"gui_usermenu", P_OFFINT (gui_usermenu), TYPE_BOOL}, + {"gui_win_height", P_OFFINT (mainwindow_height), TYPE_INT}, + {"gui_win_left", P_OFFINT (mainwindow_left), TYPE_INT}, + {"gui_win_save", P_OFFINT (mainwindow_save), TYPE_BOOL}, + {"gui_win_state", P_OFFINT (gui_win_state), TYPE_INT}, + {"gui_win_top", P_OFFINT (mainwindow_top), TYPE_INT}, + {"gui_win_width", P_OFFINT (mainwindow_width), TYPE_INT}, + +#ifdef WIN32 + {"identd", P_OFFINT (identd), TYPE_BOOL}, +#endif + {"input_balloon_chans", P_OFFINT (input_balloon_chans), TYPE_BOOL}, + {"input_balloon_hilight", P_OFFINT (input_balloon_hilight), TYPE_BOOL}, + {"input_balloon_priv", P_OFFINT (input_balloon_priv), TYPE_BOOL}, + {"input_balloon_time", P_OFFINT (input_balloon_time), TYPE_INT}, + {"input_beep_chans", P_OFFINT (input_beep_chans), TYPE_BOOL}, + {"input_beep_hilight", P_OFFINT (input_beep_hilight), TYPE_BOOL}, + {"input_beep_msg", P_OFFINT (input_beep_priv), TYPE_BOOL}, + {"input_command_char", P_OFFSET (cmdchar), TYPE_STR}, + {"input_filter_beep", P_OFFINT (filterbeep), TYPE_BOOL}, + {"input_flash_chans", P_OFFINT (input_flash_chans), TYPE_BOOL}, + {"input_flash_hilight", P_OFFINT (input_flash_hilight), TYPE_BOOL}, + {"input_flash_priv", P_OFFINT (input_flash_priv), TYPE_BOOL}, + {"input_perc_ascii", P_OFFINT (perc_ascii), TYPE_BOOL}, + {"input_perc_color", P_OFFINT (perc_color), TYPE_BOOL}, + {"input_tray_chans", P_OFFINT (input_tray_chans), TYPE_BOOL}, + {"input_tray_hilight", P_OFFINT (input_tray_hilight), TYPE_BOOL}, + {"input_tray_priv", P_OFFINT (input_tray_priv), TYPE_BOOL}, + + {"irc_auto_rejoin", P_OFFINT (autorejoin), TYPE_BOOL}, + {"irc_ban_type", P_OFFINT (bantype), TYPE_INT}, + {"irc_conf_mode", P_OFFINT (confmode), TYPE_BOOL}, + {"irc_extra_hilight", P_OFFSET (irc_extra_hilight), TYPE_STR}, + {"irc_hide_version", P_OFFINT (hidever), TYPE_BOOL}, + {"irc_id_ntext", P_OFFSET (irc_id_ntext), TYPE_STR}, + {"irc_id_ytext", P_OFFSET (irc_id_ytext), TYPE_STR}, + {"irc_invisible", P_OFFINT (invisible), TYPE_BOOL}, + {"irc_join_delay", P_OFFINT (irc_join_delay), TYPE_INT}, + {"irc_logging", P_OFFINT (logging), TYPE_BOOL}, + {"irc_logmask", P_OFFSET (logmask), TYPE_STR}, + {"irc_nick1", P_OFFSET (nick1), TYPE_STR}, + {"irc_nick2", P_OFFSET (nick2), TYPE_STR}, + {"irc_nick3", P_OFFSET (nick3), TYPE_STR}, + {"irc_nick_hilight", P_OFFSET (irc_nick_hilight), TYPE_STR}, + {"irc_no_hilight", P_OFFSET (irc_no_hilight), TYPE_STR}, + {"irc_part_reason", P_OFFSET (partreason), TYPE_STR}, + {"irc_quit_reason", P_OFFSET (quitreason), TYPE_STR}, + {"irc_raw_modes", P_OFFINT (raw_modes), TYPE_BOOL}, + {"irc_real_name", P_OFFSET (realname), TYPE_STR}, + {"irc_servernotice", P_OFFINT (servernotice), TYPE_BOOL}, + {"irc_skip_motd", P_OFFINT (skipmotd), TYPE_BOOL}, + {"irc_user_name", P_OFFSET (username), TYPE_STR}, + {"irc_wallops", P_OFFINT (wallops), TYPE_BOOL}, + {"irc_who_join", P_OFFINT (userhost), TYPE_BOOL}, + {"irc_whois_front", P_OFFINT (irc_whois_front), TYPE_BOOL}, + + {"net_auto_reconnect", P_OFFINT (autoreconnect), TYPE_BOOL}, + {"net_auto_reconnectonfail", P_OFFINT (autoreconnectonfail), TYPE_BOOL}, + {"net_bind_host", P_OFFSET (hostname), TYPE_STR}, + {"net_ping_timeout", P_OFFINT (pingtimeout), TYPE_INT}, + {"net_proxy_auth", P_OFFINT (proxy_auth), TYPE_BOOL}, + {"net_proxy_host", P_OFFSET (proxy_host), TYPE_STR}, + {"net_proxy_pass", P_OFFSET (proxy_pass), TYPE_STR}, + {"net_proxy_port", P_OFFINT (proxy_port), TYPE_INT}, + {"net_proxy_type", P_OFFINT (proxy_type), TYPE_INT}, + {"net_proxy_use", P_OFFINT (proxy_use), TYPE_INT}, + {"net_proxy_user", P_OFFSET (proxy_user), TYPE_STR}, + + {"net_reconnect_delay", P_OFFINT (recon_delay), TYPE_INT}, + {"net_throttle", P_OFFINT (throttle), TYPE_BOOL}, + + {"notify_timeout", P_OFFINT (notify_timeout), TYPE_INT}, + {"notify_whois_online", P_OFFINT (whois_on_notifyonline), TYPE_BOOL}, + + {"perl_warnings", P_OFFINT (perlwarnings), TYPE_BOOL}, + + {"sound_command", P_OFFSET (soundcmd), TYPE_STR}, + {"sound_dir", P_OFFSET (sounddir), TYPE_STR}, + {"stamp_log", P_OFFINT (timestamp_logs), TYPE_BOOL}, + {"stamp_log_format", P_OFFSET (timestamp_log_format), TYPE_STR}, + {"stamp_text", P_OFFINT (timestamp), TYPE_BOOL}, + {"stamp_text_format", P_OFFSET (stamp_format), TYPE_STR}, + + {"tab_chans", P_OFFINT (tabchannels), TYPE_BOOL}, + {"tab_dialogs", P_OFFINT (privmsgtab), TYPE_BOOL}, + {"tab_layout", P_OFFINT (tab_layout), TYPE_INT}, + {"tab_new_to_front", P_OFFINT (newtabstofront), TYPE_INT}, + {"tab_notices", P_OFFINT (notices_tabs), TYPE_BOOL}, + {"tab_pos", P_OFFINT (tab_pos), TYPE_INT}, + {"tab_position", P_OFFINT (_tabs_position), TYPE_INT}, /* obsolete */ + {"tab_server", P_OFFINT (use_server_tab), TYPE_BOOL}, + {"tab_small", P_OFFINT (tab_small), TYPE_INT}, + {"tab_sort", P_OFFINT (tab_sort), TYPE_BOOL}, + {"tab_trunc", P_OFFINT (truncchans), TYPE_INT}, + {"tab_utils", P_OFFINT (windows_as_tabs), TYPE_BOOL}, + + {"text_background", P_OFFSET (background), TYPE_STR}, + {"text_color_nicks", P_OFFINT (colorednicks), TYPE_BOOL}, + {"text_font", P_OFFSET (font_normal), TYPE_STR}, + {"text_indent", P_OFFINT (indent_nicks), TYPE_BOOL}, + {"text_max_indent", P_OFFINT (max_auto_indent), TYPE_INT}, + {"text_max_lines", P_OFFINT (max_lines), TYPE_INT}, + {"text_replay", P_OFFINT (text_replay), TYPE_BOOL}, + {"text_show_marker", P_OFFINT (show_marker), TYPE_BOOL}, + {"text_show_sep", P_OFFINT (show_separator), TYPE_BOOL}, + {"text_stripcolor", P_OFFINT (stripcolor), TYPE_BOOL}, + {"text_thin_sep", P_OFFINT (thin_separator), TYPE_BOOL}, + {"text_tint_blue", P_OFFINT (tint_blue), TYPE_INT}, + {"text_tint_green", P_OFFINT (tint_green), TYPE_INT}, + {"text_tint_red", P_OFFINT (tint_red), TYPE_INT}, + {"text_transparent", P_OFFINT (transparent), TYPE_BOOL}, + {"text_wordwrap", P_OFFINT (wordwrap), TYPE_BOOL}, + + {0, 0, 0}, +}; + +static char * +convert_with_fallback (const char *str, const char *fallback) +{ + char *utf; + + utf = g_locale_to_utf8 (str, -1, 0, 0, 0); + if (!utf) + { + /* this can happen if CHARSET envvar is set wrong */ + /* maybe it's already utf8 (breakage!) */ + if (!g_utf8_validate (str, -1, NULL)) + utf = g_strdup (fallback); + else + utf = g_strdup (str); + } + + return utf; +} + +void +load_config (void) +{ + struct stat st; + char *cfg, *sp; + const char *username, *realname; + int res, val, i, fh; + + check_prefs_dir (); + username = g_get_user_name (); + if (!username) + username = "root"; + + realname = g_get_real_name (); + if ((realname && realname[0] == 0) || !realname) + realname = username; + + username = convert_with_fallback (username, "username"); + realname = convert_with_fallback (realname, "realname"); + + memset (&prefs, 0, sizeof (struct xchatprefs)); + + /* put in default values, anything left out is automatically zero */ + prefs.local_ip = 0xffffffff; + prefs.irc_join_delay = 3; + prefs.show_marker = 1; + prefs.newtabstofront = 2; + prefs.completion_amount = 5; + prefs.away_timeout = 60; + prefs.away_size_max = 300; + prefs.away_track = 1; + prefs.timestamp_logs = 1; + prefs.truncchans = 20; + prefs.autoresume = 1; + prefs.show_away_once = 1; + prefs.indent_nicks = 1; + prefs.thin_separator = 1; + prefs._tabs_position = 2; /* 2 = left */ + prefs.fastdccsend = 1; + prefs.wordwrap = 1; + prefs.autosave = 1; + prefs.autodialog = 1; + prefs.gui_input_spell = 1; + prefs.autoreconnect = 1; + prefs.recon_delay = 10; + prefs.text_replay = 1; + prefs.tabchannels = 1; + prefs.tab_layout = 2; /* 0=Tabs 1=Reserved 2=Tree */ + prefs.tab_sort = 1; + prefs.paned_userlist = 1; + prefs.newtabstofront = 2; + prefs.use_server_tab = 1; + prefs.privmsgtab = 1; + /*prefs.style_inputbox = 1;*/ + prefs.dccpermissions = 0600; + prefs.max_lines = 500; + prefs.mainwindow_width = 640; + prefs.mainwindow_height = 400; + prefs.dialog_width = 500; + prefs.dialog_height = 256; + prefs.gui_join_dialog = 1; + prefs.gui_quit_dialog = 1; + prefs.dcctimeout = 180; + prefs.dccstalltimeout = 60; + prefs.notify_timeout = 15; + prefs.tint_red = + prefs.tint_green = + prefs.tint_blue = 195; + prefs.auto_indent = 1; + prefs.max_auto_indent = 256; + prefs.show_separator = 1; + prefs.dcc_blocksize = 1024; + prefs.throttle = 1; + /*FIXME*/ prefs.msg_time_limit = 30; + prefs.msg_number_limit = 5; + prefs.ctcp_time_limit = 30; + prefs.ctcp_number_limit = 5; + prefs.topicbar = 1; + prefs.lagometer = 1; + prefs.throttlemeter = 1; + prefs.autoopendccrecvwindow = 1; + prefs.autoopendccsendwindow = 1; + prefs.autoopendccchatwindow = 1; + prefs.userhost = 1; + prefs.gui_url_mod = 4; /* ctrl */ + prefs.gui_tray = 1; + prefs.gui_pane_left_size = 100; + prefs.gui_pane_right_size = 100; + prefs.mainwindow_save = 1; + prefs.bantype = 2; + prefs.input_balloon_time = 20; + prefs.input_flash_priv = prefs.input_flash_hilight = 1; + prefs.input_tray_priv = prefs.input_tray_hilight = 1; + prefs.autodccsend = 2; /* browse mode */ +#ifdef WIN32 + prefs.identd = 1; +#endif + strcpy (prefs.stamp_format, "[%H:%M] "); + strcpy (prefs.timestamp_log_format, "%b %d %H:%M:%S "); + strcpy (prefs.logmask, "%n-%c.log"); + strcpy (prefs.nick_suffix, ","); + strcpy (prefs.cmdchar, "/"); + strcpy (prefs.nick1, username); + strcpy (prefs.nick2, username); + strcat (prefs.nick2, "_"); + strcpy (prefs.nick3, username); + strcat (prefs.nick3, "__"); + strcpy (prefs.realname, realname); + strcpy (prefs.username, username); +#ifdef WIN32 + strcpy (prefs.sounddir, "./sounds"); + { + char out[256]; + + if (get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\" + "Explorer\\Shell Folders", "Personal", out, sizeof (out))) + snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", out); + else + snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", get_xdir_utf8 ()); + } +#else + snprintf (prefs.sounddir, sizeof (prefs.sounddir), "%s/sounds", get_xdir_utf8 ()); + snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s/downloads", get_xdir_utf8 ()); +#endif + strcpy (prefs.doubleclickuser, "QUOTE WHOIS %s %s"); + strcpy (prefs.awayreason, _("I'm busy")); + strcpy (prefs.quitreason, _("Leaving")); + strcpy (prefs.partreason, prefs.quitreason); + strcpy (prefs.font_normal, DEF_FONT); + strcpy (prefs.dnsprogram, "host"); + strcpy (prefs.irc_no_hilight, "NickServ,ChanServ"); + + g_free ((char *)username); + g_free ((char *)realname); + + fh = open (default_file (), OFLAGS | O_RDONLY); + if (fh != -1) + { + fstat (fh, &st); + cfg = malloc (st.st_size + 1); + cfg[0] = '\0'; + i = read (fh, cfg, st.st_size); + if (i >= 0) + cfg[i] = '\0'; /* make sure cfg is NULL terminated */ + close (fh); + i = 0; + do + { + switch (vars[i].type) + { + case TYPE_STR: + cfg_get_str (cfg, vars[i].name, (char *) &prefs + vars[i].offset, + vars[i].len); + break; + case TYPE_BOOL: + case TYPE_INT: + val = cfg_get_int_with_result (cfg, vars[i].name, &res); + if (res) + *((int *) &prefs + vars[i].offset) = val; + break; + } + i++; + } + while (vars[i].name); + + free (cfg); + + } else + { +#ifndef WIN32 +#ifndef __EMX__ + /* OS/2 uses UID 0 all the time */ + if (getuid () == 0) + fe_message (_("* Running IRC as root is stupid! You should\n" + " create a User Account and use that to login.\n"), FE_MSG_WARN|FE_MSG_WAIT); +#endif +#endif /* !WIN32 */ + + mkdir_utf8 (prefs.dccdir); + mkdir_utf8 (prefs.dcc_completed_dir); + } + if (prefs.mainwindow_height < 138) + prefs.mainwindow_height = 138; + if (prefs.mainwindow_width < 106) + prefs.mainwindow_width = 106; + + sp = strchr (prefs.username, ' '); + if (sp) + sp[0] = 0; /* spaces in username would break the login */ + + /* try to make sense of old ulist/tree position settings */ + if (prefs.gui_ulist_pos == 0) + { + prefs.gui_ulist_pos = 3; /* top right */ + if (prefs._gui_ulist_left) + prefs.gui_ulist_pos = 2; /* bottom left */ + + switch (prefs._tabs_position) + { + case 0: + prefs.tab_pos = 6; /* bottom */ + break; + case 1: + prefs.tab_pos = 5; /* top */ + break; + case 2: + prefs.tab_pos = 1; /* left */ + break; + case 3: + prefs.tab_pos = 4; /* right */ + break; + case 4: + prefs.tab_pos = 1; /* (hidden)left */ + break; + case 5: + if (prefs._gui_ulist_left) + { + prefs.tab_pos = 1; /* above ulist left */ + prefs.gui_ulist_pos = 2; + } + else + { + prefs.tab_pos = 3; /* above ulist right */ + prefs.gui_ulist_pos = 4; + } + break; + } + } +} + +int +save_config (void) +{ + int fh, i; + char *new_config, *config; + + check_prefs_dir (); + + config = default_file (); + new_config = malloc (strlen (config) + 5); + strcpy (new_config, config); + strcat (new_config, ".new"); + + fh = open (new_config, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, 0600); + if (fh == -1) + { + free (new_config); + return 0; + } + + if (!cfg_put_str (fh, "version", PACKAGE_VERSION)) + { + free (new_config); + return 0; + } + + i = 0; + do + { + switch (vars[i].type) + { + case TYPE_STR: + if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset)) + { + free (new_config); + return 0; + } + break; + case TYPE_INT: + case TYPE_BOOL: + if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name)) + { + free (new_config); + return 0; + } + } + i++; + } + while (vars[i].name); + + if (close (fh) == -1) + { + free (new_config); + return 0; + } + +#ifdef WIN32 + unlink (config); /* win32 can't rename to an existing file */ +#endif + if (rename (new_config, config) == -1) + { + free (new_config); + return 0; + } + free (new_config); + + return 1; +} + +static void +set_showval (session *sess, const struct prefs *var, char *tbuf) +{ + int len, dots, j; + + len = strlen (var->name); + memcpy (tbuf, var->name, len); + dots = 29 - len; + if (dots < 0) + dots = 0; + tbuf[len++] = '\003'; + tbuf[len++] = '2'; + for (j=0;j<dots;j++) + tbuf[j + len] = '.'; + len += j; + switch (var->type) + { + case TYPE_STR: + sprintf (tbuf + len, "\0033:\017 %s\n", + (char *) &prefs + var->offset); + break; + case TYPE_INT: + sprintf (tbuf + len, "\0033:\017 %d\n", + *((int *) &prefs + var->offset)); + break; + case TYPE_BOOL: + if (*((int *) &prefs + var->offset)) + sprintf (tbuf + len, "\0033:\017 %s\n", "ON"); + else + sprintf (tbuf + len, "\0033:\017 %s\n", "OFF"); + break; + } + PrintText (sess, tbuf); +} + +static void +set_list (session * sess, char *tbuf) +{ + int i; + + i = 0; + do + { + set_showval (sess, &vars[i], tbuf); + i++; + } + while (vars[i].name); +} + +int +cfg_get_bool (char *var) +{ + int i = 0; + + do + { + if (!strcasecmp (var, vars[i].name)) + { + return *((int *) &prefs + vars[i].offset); + } + i++; + } + while (vars[i].name); + + return -1; +} + +int +cmd_set (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int wild = FALSE; + int or = FALSE; + int off = FALSE; + int quiet = FALSE; + int erase = FALSE; + int i = 0, finds = 0, found; + int idx = 2; + char *var, *val; + + if (strcasecmp (word[2], "-e") == 0) + { + idx++; + erase = TRUE; + } + + /* turn a bit OFF */ + if (strcasecmp (word[idx], "-off") == 0) + { + idx++; + off = TRUE; + } + + /* turn a bit ON */ + if (strcasecmp (word[idx], "-or") == 0 || strcasecmp (word[idx], "-on") == 0) + { + idx++; + or = TRUE; + } + + if (strcasecmp (word[idx], "-quiet") == 0) + { + idx++; + quiet = TRUE; + } + + var = word[idx]; + val = word_eol[idx+1]; + + if (!*var) + { + set_list (sess, tbuf); + return TRUE; + } + + if ((strchr (var, '*') || strchr (var, '?')) && !*val) + wild = TRUE; + + if (*val == '=') + val++; + + do + { + if (wild) + found = !match (var, vars[i].name); + else + found = strcasecmp (var, vars[i].name); + + if (found == 0) + { + finds++; + switch (vars[i].type) + { + case TYPE_STR: + if (erase || *val) + { + strncpy ((char *) &prefs + vars[i].offset, val, vars[i].len); + ((char *) &prefs)[vars[i].offset + vars[i].len - 1] = 0; + if (!quiet) + PrintTextf (sess, "%s set to: %s\n", var, (char *) &prefs + vars[i].offset); + } else + { + set_showval (sess, &vars[i], tbuf); + } + break; + case TYPE_INT: + case TYPE_BOOL: + if (*val) + { + if (vars[i].type == TYPE_BOOL) + { + if (atoi (val)) + *((int *) &prefs + vars[i].offset) = 1; + else + *((int *) &prefs + vars[i].offset) = 0; + if (!strcasecmp (val, "YES") || !strcasecmp (val, "ON")) + *((int *) &prefs + vars[i].offset) = 1; + if (!strcasecmp (val, "NO") || !strcasecmp (val, "OFF")) + *((int *) &prefs + vars[i].offset) = 0; + } else + { + if (or) + *((int *) &prefs + vars[i].offset) |= atoi (val); + else if (off) + *((int *) &prefs + vars[i].offset) &= ~(atoi (val)); + else + *((int *) &prefs + vars[i].offset) = atoi (val); + } + if (!quiet) + PrintTextf (sess, "%s set to: %d\n", var, + *((int *) &prefs + vars[i].offset)); + } else + { + set_showval (sess, &vars[i], tbuf); + } + break; + } + } + i++; + } + while (vars[i].name); + + if (!finds && !quiet) + PrintText (sess, "No such variable.\n"); + + return TRUE; +} + +int +xchat_open_file (char *file, int flags, int mode, int xof_flags) +{ + char buf[1024]; + + if (xof_flags & XOF_FULLPATH) + { + if (xof_flags & XOF_DOMODE) + return open (file, flags | OFLAGS, mode); + else + return open (file, flags | OFLAGS); + } + + snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file); + if (xof_flags & XOF_DOMODE) + return open (buf, flags | OFLAGS, mode); + else + return open (buf, flags | OFLAGS); +} + +FILE * +xchat_fopen_file (const char *file, const char *mode, int xof_flags) +{ + char buf[1024]; + + if (xof_flags & XOF_FULLPATH) + return fopen (file, mode); + + snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file); + return fopen (buf, mode); +} diff --git a/src/common/cfgfiles.h b/src/common/cfgfiles.h new file mode 100644 index 00000000..984b9472 --- /dev/null +++ b/src/common/cfgfiles.h @@ -0,0 +1,55 @@ +/* cfgfiles.h */ + +#ifndef XCHAT_CFGFILES_H +#define XCHAT_CFGFILES_H + +#include "xchat.h" + +extern char *xdir_fs; +extern char *xdir_utf; + +char *cfg_get_str (char *cfg, char *var, char *dest, int dest_len); +int cfg_get_bool (char *var); +int cfg_get_int_with_result (char *cfg, char *var, int *result); +int cfg_get_int (char *cfg, char *var); +int cfg_put_int (int fh, int value, char *var); +int cfg_get_color (char *cfg, char *var, int *r, int *g, int *b); +int cfg_put_color (int fh, int r, int g, int b, char *var); +char *get_xdir_fs (void); +char *get_xdir_utf8 (void); +void load_config (void); +int save_config (void); +void list_free (GSList ** list); +void list_loadconf (char *file, GSList ** list, char *defaultconf); +int list_delentry (GSList ** list, char *name); +void list_addentry (GSList ** list, char *cmd, char *name); +int cmd_set (session *sess, char *tbuf, char *word[], char *word_eol[]); +int xchat_open_file (char *file, int flags, int mode, int xof_flags); +FILE *xchat_fopen_file (const char *file, const char *mode, int xof_flags); +#define XOF_DOMODE 1 +#define XOF_FULLPATH 2 + +#define STRUCT_OFFSET_STR(type,field) \ +( (unsigned int) (((char *) (&(((type *) NULL)->field)))- ((char *) NULL)) ) + +#define STRUCT_OFFSET_INT(type,field) \ +( (unsigned int) (((int *) (&(((type *) NULL)->field)))- ((int *) NULL)) ) + +#define P_OFFSET(field) STRUCT_OFFSET_STR(struct xchatprefs, field),sizeof(prefs.field) +#define P_OFFSETNL(field) STRUCT_OFFSET_STR(struct xchatprefs, field) +#define P_OFFINT(field) STRUCT_OFFSET_INT(struct xchatprefs, field),0 +#define P_OFFINTNL(field) STRUCT_OFFSET_INT(struct xchatprefs, field) + +struct prefs +{ + char *name; + unsigned short offset; + unsigned short len; + unsigned short type; +}; + +#define TYPE_STR 0 +#define TYPE_INT 1 +#define TYPE_BOOL 2 + +#endif diff --git a/src/common/chanopt.c b/src/common/chanopt.c new file mode 100644 index 00000000..a4fd8faa --- /dev/null +++ b/src/common/chanopt.c @@ -0,0 +1,431 @@ +/* per-channel/dialog settings :: /CHANOPT */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include "xchat.h" + +#include "cfgfiles.h" +#include "server.h" +#include "text.h" +#include "util.h" +#include "xchatc.h" + + +static GSList *chanopt_list = NULL; +static gboolean chanopt_open = FALSE; +static gboolean chanopt_changed = FALSE; + + +typedef struct +{ + char *name; + char *alias; /* old names from 2.8.4 */ + int offset; +} channel_options; + +#define S_F(xx) STRUCT_OFFSET_STR(struct session,xx) + +static const channel_options chanopt[] = +{ + {"alert_beep", "BEEP", S_F(alert_beep)}, + {"alert_taskbar", NULL, S_F(alert_taskbar)}, + {"alert_tray", "TRAY", S_F(alert_tray)}, + + {"text_hidejoinpart", "CONFMODE", S_F(text_hidejoinpart)}, + {"text_logging", NULL, S_F(text_logging)}, + {"text_scrollback", NULL, S_F(text_scrollback)}, +}; + +#undef S_F + +static char * +chanopt_value (guint8 val) +{ + switch (val) + { + case SET_OFF: + return "OFF"; + case SET_ON: + return "ON"; + default: + return "{unset}"; + } +} + +/* handle the /CHANOPT command */ + +int +chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int dots, i = 0, j, p = 0; + guint8 val; + int offset = 2; + char *find; + gboolean quiet = FALSE; + int newval = -1; + + if (!strcmp (word[2], "-quiet")) + { + quiet = TRUE; + offset++; + } + + find = word[offset++]; + + if (word[offset][0]) + { + if (!strcasecmp (word[offset], "ON")) + newval = 1; + else if (!strcasecmp (word[offset], "OFF")) + newval = 0; + else if (word[offset][0] == 'u') + newval = SET_DEFAULT; + else + newval = atoi (word[offset]); + } + + if (!quiet) + PrintTextf (sess, "\002Network\002: %s \002Channel\002: %s\n", + sess->server->network ? server_get_network (sess->server, TRUE) : _("<none>"), + sess->channel[0] ? sess->channel : _("<none>")); + + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + if (find[0] == 0 || match (find, chanopt[i].name) || (chanopt[i].alias && match (find, chanopt[i].alias))) + { + if (newval != -1) /* set new value */ + { + *(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = newval; + } + + if (!quiet) /* print value */ + { + strcpy (tbuf, chanopt[i].name); + p = strlen (tbuf); + + tbuf[p++] = 3; + tbuf[p++] = '2'; + + dots = 20 - strlen (chanopt[i].name); + + for (j = 0; j < dots; j++) + tbuf[p++] = '.'; + tbuf[p++] = 0; + + val = G_STRUCT_MEMBER (guint8, sess, chanopt[i].offset); + PrintTextf (sess, "%s\0033:\017 %s", tbuf, chanopt_value (val)); + } + } + i++; + } + + return TRUE; +} + +/* is a per-channel setting set? Or is it UNSET and + * the global version is set? */ + +gboolean +chanopt_is_set (unsigned int global, guint8 per_chan_setting) +{ + if (per_chan_setting == SET_DEFAULT) + return global; + + return per_chan_setting; +} + +/* additive version */ + +gboolean +chanopt_is_set_a (unsigned int global, guint8 per_chan_setting) +{ + if (per_chan_setting == SET_DEFAULT) + return global; + + return per_chan_setting || global; +} + +/* === below is LOADING/SAVING stuff only === */ + +typedef struct +{ + /* Per-Channel Alerts */ + /* use a byte, because we need a pointer to each element */ + guint8 alert_beep; + guint8 alert_taskbar; + guint8 alert_tray; + + /* Per-Channel Settings */ + guint8 text_hidejoinpart; + guint8 text_logging; + guint8 text_scrollback; + + char *network; + char *channel; + +} chanopt_in_memory; + + +static chanopt_in_memory * +chanopt_find (char *network, char *channel, gboolean add_new) +{ + GSList *list; + chanopt_in_memory *co; + int i; + + for (list = chanopt_list; list; list = list->next) + { + co = list->data; + if (!strcasecmp (co->channel, channel) && + !strcasecmp (co->network, network)) + return co; + } + + if (!add_new) + return NULL; + + /* allocate a new one */ + co = g_malloc0 (sizeof (chanopt_in_memory)); + co->channel = g_strdup (channel); + co->network = g_strdup (network); + + /* set all values to SET_DEFAULT */ + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + *(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = SET_DEFAULT; + i++; + } + + chanopt_list = g_slist_prepend (chanopt_list, co); + chanopt_changed = TRUE; + + return co; +} + +static void +chanopt_add_opt (chanopt_in_memory *co, char *var, int new_value) +{ + int i; + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + if (!strcmp (var, chanopt[i].name)) + { + *(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = new_value; + + } + i++; + } +} + +/* load chanopt.conf from disk into our chanopt_list GSList */ + +static void +chanopt_load_all (void) +{ + int fh; + char buf[256]; + char *eq; + char *network = NULL; + chanopt_in_memory *current = NULL; + + /* 1. load the old file into our GSList */ + fh = xchat_open_file ("chanopt.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + while (waitline (fh, buf, sizeof buf, FALSE) != -1) + { + eq = strchr (buf, '='); + if (!eq) + continue; + eq[0] = 0; + + if (eq != buf && eq[-1] == ' ') + eq[-1] = 0; + + if (!strcmp (buf, "network")) + { + g_free (network); + network = g_strdup (eq + 2); + } + else if (!strcmp (buf, "channel")) + { + current = chanopt_find (network, eq + 2, TRUE); + chanopt_changed = FALSE; + } + else + { + if (current) + chanopt_add_opt (current, buf, atoi (eq + 2)); + } + + } + close (fh); + g_free (network); + } +} + +void +chanopt_load (session *sess) +{ + int i; + guint8 val; + chanopt_in_memory *co; + char *network; + + if (sess->channel[0] == 0) + return; + + network = server_get_network (sess->server, FALSE); + if (!network) + return; + + if (!chanopt_open) + { + chanopt_open = TRUE; + chanopt_load_all (); + } + + co = chanopt_find (network, sess->channel, FALSE); + if (!co) + return; + + /* fill in all the sess->xxxxx fields */ + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + val = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset); + *(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = val; + i++; + } +} + +void +chanopt_save (session *sess) +{ + int i; + guint8 vals; + guint8 valm; + chanopt_in_memory *co; + char *network; + + if (sess->channel[0] == 0) + return; + + network = server_get_network (sess->server, FALSE); + if (!network) + return; + + /* 2. reconcile sess with what we loaded from disk */ + + co = chanopt_find (network, sess->channel, TRUE); + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + vals = G_STRUCT_MEMBER(guint8, sess, chanopt[i].offset); + valm = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset); + + if (vals != valm) + { + *(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = vals; + chanopt_changed = TRUE; + } + + i++; + } +} + +static void +chanopt_save_one_channel (chanopt_in_memory *co, int fh) +{ + int i; + char buf[256]; + guint8 val; + + snprintf (buf, sizeof (buf), "%s = %s\n", "network", co->network); + write (fh, buf, strlen (buf)); + + snprintf (buf, sizeof (buf), "%s = %s\n", "channel", co->channel); + write (fh, buf, strlen (buf)); + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset); + if (val != SET_DEFAULT) + { + snprintf (buf, sizeof (buf), "%s = %d\n", chanopt[i].name, val); + write (fh, buf, strlen (buf)); + } + i++; + } +} + +void +chanopt_save_all (void) +{ + int i; + int num_saved; + int fh; + GSList *list; + chanopt_in_memory *co; + guint8 val; + + if (!chanopt_list || !chanopt_changed) + { + return; + } + + fh = xchat_open_file ("chanopt.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh == -1) + { + return; + } + + for (num_saved = 0, list = chanopt_list; list; list = list->next) + { + co = list->data; + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset); + /* not using global/default setting, must save */ + if (val != SET_DEFAULT) + { + if (num_saved != 0) + write (fh, "\n", 1); + + chanopt_save_one_channel (co, fh); + num_saved++; + goto cont; + } + i++; + } + +cont: + g_free (co->network); + g_free (co->channel); + g_free (co); + } + + close (fh); + + /* we're quiting, no need to free */ + + /*g_slist_free (chanopt_list); + chanopt_list = NULL; + + chanopt_open = FALSE; + chanopt_changed = FALSE;*/ +} diff --git a/src/common/chanopt.h b/src/common/chanopt.h new file mode 100644 index 00000000..90ca86c3 --- /dev/null +++ b/src/common/chanopt.h @@ -0,0 +1,6 @@ +int chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[]); +gboolean chanopt_is_set (unsigned int global, guint8 per_chan_setting); +gboolean chanopt_is_set_a (unsigned int global, guint8 per_chan_setting); +void chanopt_save_all (void); +void chanopt_save (session *sess); +void chanopt_load (session *sess); diff --git a/src/common/ctcp.c b/src/common/ctcp.c new file mode 100644 index 00000000..574cda79 --- /dev/null +++ b/src/common/ctcp.c @@ -0,0 +1,191 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#include "xchat.h" +#include "cfgfiles.h" +#include "util.h" +#include "modes.h" +#include "outbound.h" +#include "ignore.h" +#include "inbound.h" +#include "dcc.h" +#include "text.h" +#include "ctcp.h" +#include "server.h" +#include "xchatc.h" + + +static void +ctcp_reply (session *sess, char *nick, char *word[], char *word_eol[], + char *conf) +{ + char tbuf[4096]; /* can receive 2048 from IRC, so this is enough */ + + conf = strdup (conf); + /* process %C %B etc */ + check_special_chars (conf, TRUE); + auto_insert (tbuf, sizeof (tbuf), conf, word, word_eol, "", "", word_eol[5], + server_get_network (sess->server, TRUE), "", "", nick); + free (conf); + handle_command (sess, tbuf, FALSE); +} + +static int +ctcp_check (session *sess, char *nick, char *word[], char *word_eol[], + char *ctcp) +{ + int ret = 0; + char *po; + struct popup *pop; + GSList *list = ctcp_list; + + po = strchr (ctcp, '\001'); + if (po) + *po = 0; + + po = strchr (word_eol[5], '\001'); + if (po) + *po = 0; + + while (list) + { + pop = (struct popup *) list->data; + if (!strcasecmp (ctcp, pop->name)) + { + ctcp_reply (sess, nick, word, word_eol, pop->cmd); + ret = 1; + } + list = list->next; + } + return ret; +} + +void +ctcp_handle (session *sess, char *to, char *nick, char *ip, + char *msg, char *word[], char *word_eol[], int id) +{ + char *po; + session *chansess; + server *serv = sess->server; + char outbuf[1024]; + int ctcp_offset = 2; + + if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') ) + ctcp_offset = 3; + + /* consider DCC to be different from other CTCPs */ + if (!strncasecmp (msg, "DCC", 3)) + { + /* but still let CTCP replies override it */ + if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) + { + if (!ignore_check (word[1], IG_DCC)) + handle_dcc (sess, nick, word, word_eol); + } + return; + } + + /* consider ACTION to be different from other CTCPs. Check + ignore as if it was a PRIV/CHAN. */ + if (!strncasecmp (msg, "ACTION ", 7)) + { + if (is_channel (serv, to)) + { + /* treat a channel action as a CHAN */ + if (ignore_check (word[1], IG_CHAN)) + return; + } else + { + /* treat a private action as a PRIV */ + if (ignore_check (word[1], IG_PRIV)) + return; + } + + /* but still let CTCP replies override it */ + if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) + goto generic; + + inbound_action (sess, to, nick, ip, msg + 7, FALSE, id); + return; + } + + if (ignore_check (word[1], IG_CTCP)) + return; + + if (!strcasecmp (msg, "VERSION") && !prefs.hidever) + { + snprintf (outbuf, sizeof (outbuf), "VERSION xchat "PACKAGE_VERSION" %s", + get_cpu_str ()); + serv->p_nctcp (serv, nick, outbuf); + } + + if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) + { + if (!strncasecmp (msg, "SOUND", 5)) + { + po = strchr (word[5], '\001'); + if (po) + po[0] = 0; + + if (is_channel (sess->server, to)) + { + chansess = find_channel (sess->server, to); + if (!chansess) + chansess = sess; + + EMIT_SIGNAL (XP_TE_CTCPSNDC, chansess, word[5], + nick, to, NULL, 0); + } else + { + EMIT_SIGNAL (XP_TE_CTCPSND, sess->server->front_session, word[5], + nick, NULL, NULL, 0); + } + + /* don't let IRCers specify path */ +#ifdef WIN32 + if (strchr (word[5], '/') == NULL && strchr (word[5], '\\') == NULL) +#else + if (strchr (word[5], '/') == NULL) +#endif + sound_play (word[5], TRUE); + return; + } + } + +generic: + po = strchr (msg, '\001'); + if (po) + po[0] = 0; + + if (!is_channel (sess->server, to)) + { + EMIT_SIGNAL (XP_TE_CTCPGEN, sess->server->front_session, msg, nick, + NULL, NULL, 0); + } else + { + chansess = find_channel (sess->server, to); + if (!chansess) + chansess = sess; + EMIT_SIGNAL (XP_TE_CTCPGENC, chansess, msg, nick, to, NULL, 0); + } +} diff --git a/src/common/ctcp.h b/src/common/ctcp.h new file mode 100644 index 00000000..9acd80e3 --- /dev/null +++ b/src/common/ctcp.h @@ -0,0 +1,6 @@ +#ifndef XCHAT_CTCP_H +#define XCHAT_CTCP_H + +void ctcp_handle (session *sess, char *to, char *nick, char *ip, char *msg, char *word[], char *word_eol[], int id); + +#endif diff --git a/src/common/dbus/Makefile.am b/src/common/dbus/Makefile.am new file mode 100644 index 00000000..05ee9de6 --- /dev/null +++ b/src/common/dbus/Makefile.am @@ -0,0 +1,56 @@ +noinst_LIBRARIES = libxchatdbus.a +libxchatdbus_a_SOURCES = \ + dbus-plugin.c \ + dbus-plugin.h \ + dbus-client.c \ + dbus-client.h + +EXTRA_DIST = \ + remote-object.xml \ + apps_xchat_url_handler.schemas \ + marshallers.list \ + example.py \ + org.xchat.service.service.in \ + README + +BUILT_SOURCES = \ + marshallers.h \ + remote-object-glue.h + +CLEANFILES = $(BUILT_SOURCES) + +INCLUDES = $(COMMON_CFLAGS) $(DBUS_CFLAGS) + +noinst_PROGRAMS = example +example_SOURCES = example.c +example_LDADD = $(DBUS_LIBS) $(GLIB_LIBS) + +remote-object-glue.h: remote-object.xml + $(LIBTOOL) --mode=execute $(DBUS_BINDING_TOOL) --prefix=remote_object --mode=glib-server --output=$@ $< + +marshallers.h: marshallers.list + $(LIBTOOL) --mode=execute $(GLIB_GENMARSHAL) --header --body $< > $@ + +# Dbus service file +servicedir = $(DBUS_SERVICES_DIR) +service_in_files = org.xchat.service.service.in +service_DATA = $(service_in_files:.service.in=.service) + +# Rule to make the service file with bindir expanded +$(service_DATA): $(service_in_files) Makefile + @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ + +if DO_GCONF +GCONF_SCHEMA_CONFIG_SOURCE = `$(GCONFTOOL) --get-default-source` +GCONF_SCHEMA_FILE_DIR = $(sysconfdir)/gconf/schemas +schemadir = $(GCONF_SCHEMA_FILE_DIR) +schema_DATA = apps_xchat_url_handler.schemas +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$$p; \ + done \ + fi +else +install-data-local: +endif diff --git a/src/common/dbus/README b/src/common/dbus/README new file mode 100644 index 00000000..d61cf4e0 --- /dev/null +++ b/src/common/dbus/README @@ -0,0 +1,198 @@ +For more help you can see the xchat plugin interface documentation. +http://www.xchat.org/docs/plugin20.html +WARNING: The dbus interface may change in the future. + +You can use the "/org/xchat/Remote" object with interface "org.xchat.plugin", +but his context can be changed by other clients at any moment and +you may receive signal asked by other clients. So for more complex usage it's +better to get your own remote object. Using "Connect" method on interface +"org.xchat.connection" + +Available methods on org.xchat.connection interface: + +"Connect" + - Parameters: + - gchar*: filename + - gchar*: name + - gchar*: description + - gchar*: version + - Returns: + - gchar*: Your own object's path. + +"Disconnect" + No parameter, no return value. It frees your remote object. + +Available methods on org.xchat.plugin interface: + +"Command" + - Parameters: + - gchar*: the command name without the "/". (e.g. "nick pseudo") + +"Print" + - Parameters: + - gchar*: text to print on the xchat window. + +"FindContext" + - Parameters: + - gchar*: the server name. Can be NULL. + - gchar*: the channel name. Can be NULL. + - Returns: + - guint: context ID + +"GetContext" + - Returns: + - guint: current context's ID + +"SetContext" + - Parameters: + - guint: context ID to switch, returned by "FindContext" or "GetContext" + - Returns: + - gboolean: 1 for success, 0 for failure. + +"GetInfo" + - Parameters: + - gchar*: ID of the information you want. + - Returns: + - gchar*: information you requested. + +"GetPrefs" + - Parameters: + - gchar*: Setting name required. + - Returns: + - int: 0-Failed 1-Returned a string 2-Returned an Integer + 3-Returned a Boolean. + - gchar*: the information requested if it's a string. + - int: the information requested if it's a integer or boolean. + +"HookCommand" + - Parameters: + - gchar*: Name of the command (without the forward slash). + - int: Priority of this command. + - gchar*: String of text to display when the user executes /help + for this command. May be NULL if you're lazy. + - int: Value to returns when the command is catched. See XCHAT_EAT_*. + - Returns: + - guint: The ID of the hook. + +"HookServer" + - Parameters: + - gchar*: Name of the server event. + - int: Priority of this command. + - int: Value to returns when the command is catched. See XCHAT_EAT_*. + - Returns: + - guint: The ID of the hook. + +"HookPrint" + - Parameters: + - gchar*: Name of the print event. + - int: Priority of this command. + - int: Value to returns when the command is catched. See XCHAT_EAT_*. + - Returns: + - guint: The ID of the hook. + +"Unhook" + - Parameters: + - guint: ID of the hook to unhook. + (the return value of "HookCommand", "HookServer" or "HookPrint") + +"ListGet" + - Parameters: + - gchar*: The list name. + - Returns: + - guint: List ID. + +"ListNext" + - Parameters: + - guint: List ID returned by "ListGet". + - Returns: + - gboolean: says if there is no more item in the list. + +"ListStr" + - Parameters: + - guint: List ID returned by "ListGet". + - gchar*: Name of the information needed. + - Returns: + - gchar*: The information requested. +Warning: "context" attribut of "channels" list should be get with "ListInt" + +"ListInt" + - Parameters: + - guint: List ID returned by "ListGet". + - gchar*: Name of the information needed. + - Returns: + - guint: The information requested. + +"ListTime" + - Parameters: + - guint: List ID returned by "ListGet". + - gchar*: Name of the information needed. + - Returns: + - guint64: The information requested. + +"ListFields" + - Parameters: + - gchar*: The list name. + - Returns: + - gchar**: information names in this list. + +"ListFree" + - Parameters: + - guint: List ID returned by "ListGet". + +"EmitPrint" + - Parameters: + - gchar*: Text event to print. + - gchar**: NULL terminated array of string. + - Returns: + - gboolean: 1-Success 0-Failure. + +"Nickcmp" + - Parameters: + - gchar*: String to compare. + - gchar*: String to compare. + - Returns: + - int: An integer less than, equal to, or greater than zero if s1 is found, + respectively, to be less than, to match, or be greater than s2. + +"Strip" + - Parameters: + - gchar*: String to strip. + - int: Length of the string (or -1 for NULL terminated). + - int: Bit-field of flags: 0-Strip mIRC colors, 1-Strip text attributes. + - Returns: + - gchar*: striped string. + +"SendModes" + - Parameters: + - gchar**: NULL terminated array of targets (strings). The names of people + whom the action will be performed on. + - int: Maximum modes to send per line. + - gchar: Mode sign, '-' or '+'. + - gchar: Mode char, e.g. 'o' for Ops. + + +Available signals: + +"ServerSignal" + - Parameters: + - gchar**: word returned by xchat. + - gchar**: word_eol returned bu xchat. + - guint: the ID of the hook. (the return value of "HookServer"). + - guint: the ID of the context where the event come from. + +"CommandSignal" + - Parameters: + - gchar**: word returned by xchat. + - gchar**: word_eol returned bu xchat. + - guint: the ID of the hook. (the return value of "HookCommand"). + - guint: the ID of the context where the event come from. + +"PrintSignal" + - Parameters: + - gchar**: word returned by xchat. + - guint: the ID of the hook. (the return value of "HookPrint"). + - guint: the ID of the context where the event come from. + +"UnloadSignal" + emited when the user asks to unload your program. + Please exit(0); when received ! diff --git a/src/common/dbus/apps_xchat_url_handler.schemas b/src/common/dbus/apps_xchat_url_handler.schemas new file mode 100644 index 00000000..10ac948d --- /dev/null +++ b/src/common/dbus/apps_xchat_url_handler.schemas @@ -0,0 +1,37 @@ +<gconfschemafile> + <schemalist> + <schema> + <key>/schemas/desktop/gnome/url-handlers/irc/command</key> + <applyto>/desktop/gnome/url-handlers/irc/command</applyto> + <owner>xchat</owner> + <type>string</type> + <default>xchat --existing --url=%u</default> + <locale name="C"> + <short>The handler for "irc://" URLs</short> + </locale> + </schema> + + <schema> + <key>/schemas/desktop/gnome/url-handlers/irc/enabled</key> + <applyto>/desktop/gnome/url-handlers/irc/enabled</applyto> + <owner>xchat</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Set it at TRUE if you want it activated</short> + </locale> + </schema> + + <schema> + <key>/schemas/desktop/gnome/url-handlers/irc/needs_terminal</key> + <applyto>/desktop/gnome/url-handlers/irc/needs_terminal</applyto> + <owner>xchat</owner> + <type>bool</type> + <default>false</default> + <locale name="C"> + <short>Run xchat in a terminal?</short> + </locale> + </schema> + + </schemalist> +</gconfschemafile> diff --git a/src/common/dbus/dbus-client.c b/src/common/dbus/dbus-client.c new file mode 100644 index 00000000..ac6bf6dc --- /dev/null +++ b/src/common/dbus/dbus-client.c @@ -0,0 +1,118 @@ +/* dbus-client.c - XChat command-line options for D-Bus + * Copyright (C) 2006 Claessens Xavier + * + * 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 + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#include <dbus/dbus-glib.h> +#include "dbus-client.h" +#include "../xchat.h" +#include "../xchatc.h" + +#define DBUS_SERVICE "org.xchat.service" +#define DBUS_REMOTE "/org/xchat/Remote" +#define DBUS_REMOTE_INTERFACE "org.xchat.plugin" + +static void +write_error (char *message, + GError **error) +{ + if (error == NULL || *error == NULL) { + return; + } + g_printerr ("%s: %s\n", message, (*error)->message); + g_clear_error (error); +} + +void +xchat_remote (void) +/* TODO: dbus_g_connection_unref (connection) are commented because it makes + * dbus to crash. Fixed in dbus >=0.70 ?!? + * https://launchpad.net/distros/ubuntu/+source/dbus/+bug/54375 + */ +{ + DBusGConnection *connection; + DBusGProxy *dbus = NULL; + DBusGProxy *remote_object = NULL; + gboolean xchat_running; + GError *error = NULL; + char *command = NULL; + + /* GnomeVFS >=2.15 uses D-Bus and threads, so threads should be + * initialised before opening for the first time a D-Bus connection */ + if (!g_thread_supported ()) { + g_thread_init (NULL); + } + dbus_g_thread_init (); + + /* if there is nothing to do, return now. */ + if (!arg_existing || !(arg_url || arg_command)) { + return; + } + + arg_dont_autoconnect = TRUE; + + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (!connection) { + write_error (_("Couldn't connect to session bus"), &error); + return; + } + + /* Checks if xchat is already running */ + dbus = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + if (!dbus_g_proxy_call (dbus, "NameHasOwner", &error, + G_TYPE_STRING, DBUS_SERVICE, + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &xchat_running, + G_TYPE_INVALID)) { + write_error (_("Failed to complete NameHasOwner"), &error); + xchat_running = FALSE; + } + g_object_unref (dbus); + + if (!xchat_running) { + //dbus_g_connection_unref (connection); + return; + } + + remote_object = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE, + DBUS_REMOTE, + DBUS_REMOTE_INTERFACE); + + if (arg_url) { + command = g_strdup_printf ("url %s", arg_url); + } else if (arg_command) { + command = g_strdup (arg_command); + } + + if (command) { + if (!dbus_g_proxy_call (remote_object, "Command", + &error, + G_TYPE_STRING, command, + G_TYPE_INVALID,G_TYPE_INVALID)) { + write_error (_("Failed to complete Command"), &error); + } + g_free (command); + } + + exit (0); +} diff --git a/src/common/dbus/dbus-client.h b/src/common/dbus/dbus-client.h new file mode 100644 index 00000000..cafc2b71 --- /dev/null +++ b/src/common/dbus/dbus-client.h @@ -0,0 +1,27 @@ +/* dbus-client.h - XChat command-line options for D-Bus + * Copyright (C) 2006 Claessens Xavier + * + * 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 + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#ifndef __DBUS_PLUGIN_H__ +#define __DBUS_PLUGIN_H__ + +void xchat_remote (void); + +#endif /* __DBUS_PLUGIN_H__ */ diff --git a/src/common/dbus/dbus-plugin.c b/src/common/dbus/dbus-plugin.c new file mode 100644 index 00000000..012812cc --- /dev/null +++ b/src/common/dbus/dbus-plugin.c @@ -0,0 +1,1087 @@ +/* dbus-plugin.c - xchat plugin for remote access using D-Bus + * Copyright (C) 2006 Claessens Xavier + * + * 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 + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#define DBUS_API_SUBJECT_TO_CHANGE + +#include <config.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include <glib/gi18n.h> +#include "../xchat-plugin.h" + +#define PNAME _("remote access") +#define PDESC _("plugin for remote access using DBUS") +#define PVERSION "" + +#define DBUS_SERVICE "org.xchat.service" +#define DBUS_OBJECT_PATH "/org/xchat" + +static xchat_plugin *ph; +static guint last_context_id = 0; +static GList *contexts = NULL; +static GHashTable *clients = NULL; +static DBusGConnection *connection; + +typedef struct RemoteObject RemoteObject; +typedef struct RemoteObjectClass RemoteObjectClass; + +GType Remote_object_get_type (void); + +struct RemoteObject +{ + GObject parent; + + guint last_hook_id; + guint last_list_id; + xchat_context *context; + char *dbus_path; + char *filename; + GHashTable *hooks; + GHashTable *lists; + void *handle; +}; + +struct RemoteObjectClass +{ + GObjectClass parent; +}; + +typedef struct +{ + guint id; + int return_value; + xchat_hook *hook; + RemoteObject *obj; +} HookInfo; + +typedef struct +{ + guint id; + xchat_context *context; +} ContextInfo; + +enum +{ + SERVER_SIGNAL, + COMMAND_SIGNAL, + PRINT_SIGNAL, + UNLOAD_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define REMOTE_TYPE_OBJECT (remote_object_get_type ()) +#define REMOTE_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), REMOTE_TYPE_OBJECT, RemoteObject)) +#define REMOTE_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), REMOTE_TYPE_OBJECT, RemoteObjectClass)) +#define REMOTE_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), REMOTE_TYPE_OBJECT)) +#define REMOTE_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), REMOTE_TYPE_OBJECT)) +#define REMOTE_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), REMOTE_TYPE_OBJECT, RemoteObjectClass)) +#define REMOTE_OBJECT_ERROR (remote_object_error_quark ()) +#define REMOTE_TYPE_ERROR (remote_object_error_get_type ()) + +G_DEFINE_TYPE (RemoteObject, remote_object, G_TYPE_OBJECT) + +/* Available services */ + +static gboolean remote_object_connect (RemoteObject *obj, + const char *filename, + const char *name, + const char *desc, + const char *version, + DBusGMethodInvocation *context); + +static gboolean remote_object_disconnect (RemoteObject *obj, + DBusGMethodInvocation *context); + +static gboolean remote_object_command (RemoteObject *obj, + const char *command, + GError **error); + +static gboolean remote_object_print (RemoteObject *obj, + const char *text, + GError **error); + +static gboolean remote_object_find_context (RemoteObject *obj, + const char *server, + const char *channel, + guint *ret_id, + GError **error); + +static gboolean remote_object_get_context (RemoteObject *obj, + guint *ret_id, + GError **error); + +static gboolean remote_object_set_context (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error); + +static gboolean remote_object_print (RemoteObject *obj, + const char *text, + GError **error); + +static gboolean remote_object_get_info (RemoteObject *obj, + const char *id, + char **ret_info, + GError **error); + +static gboolean remote_object_get_prefs (RemoteObject *obj, + const char *name, + int *ret_type, + char **ret_str, + int *ret_int, + GError **error); + +static gboolean remote_object_hook_command (RemoteObject *obj, + const char *name, + int pri, + const char *help_text, + int return_value, + guint *ret_id, + GError **error); + +static gboolean remote_object_hook_server (RemoteObject *obj, + const char *name, + int pri, + int return_value, + guint *ret_id, + GError **error); + +static gboolean remote_object_hook_print (RemoteObject *obj, + const char *name, + int pri, + int return_value, + guint *ret_id, + GError **error); + +static gboolean remote_object_unhook (RemoteObject *obj, + guint id, + GError **error); + +static gboolean remote_object_list_get (RemoteObject *obj, + const char *name, + guint *ret_id, + GError **error); + +static gboolean remote_object_list_next (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error); + +static gboolean remote_object_list_str (RemoteObject *obj, + guint id, + const char *name, + char **ret_str, + GError **error); + +static gboolean remote_object_list_int (RemoteObject *obj, + guint id, + const char *name, + int *ret_int, + GError **error); + +static gboolean remote_object_list_time (RemoteObject *obj, + guint id, + const char *name, + guint64 *ret_time, + GError **error); + +static gboolean remote_object_list_fields (RemoteObject *obj, + const char *name, + char ***ret, + GError **error); + +static gboolean remote_object_list_free (RemoteObject *obj, + guint id, + GError **error); + +static gboolean remote_object_emit_print (RemoteObject *obj, + const char *event_name, + const char *args[], + gboolean *ret, + GError **error); + +static gboolean remote_object_nickcmp (RemoteObject *obj, + const char *nick1, + const char *nick2, + int *ret, + GError **error); + +static gboolean remote_object_strip (RemoteObject *obj, + const char *str, + int len, + int flag, + char **ret_str, + GError **error); + +static gboolean remote_object_send_modes (RemoteObject *obj, + const char *targets[], + int modes_per_line, + char sign, + char mode, + GError **error); + +#include "remote-object-glue.h" +#include "marshallers.h" + +/* Useful functions */ + +static char** build_list (char *word[]); +static guint context_list_find_id (xchat_context *context); +static xchat_context* context_list_find_context (guint id); + +/* Remote Object */ + +static void +hook_info_destroy (gpointer data) +{ + HookInfo *info = (HookInfo*)data; + + if (info == NULL) { + return; + } + xchat_unhook (ph, info->hook); + g_free (info); +} + +static void +list_info_destroy (gpointer data) +{ + xchat_list_free (ph, (xchat_list*)data); +} + +static void +remote_object_finalize (GObject *obj) +{ + RemoteObject *self = (RemoteObject*)obj; + + g_hash_table_destroy (self->lists); + g_hash_table_destroy (self->hooks); + g_free (self->dbus_path); + g_free (self->filename); + xchat_plugingui_remove (ph, self->handle); + + G_OBJECT_CLASS (remote_object_parent_class)->finalize (obj); +} + +static void +remote_object_init (RemoteObject *obj) +{ + obj->hooks = + g_hash_table_new_full (g_int_hash, + g_int_equal, + NULL, + hook_info_destroy); + + obj->lists = + g_hash_table_new_full (g_int_hash, + g_int_equal, + g_free, + list_info_destroy); + obj->dbus_path = NULL; + obj->filename = NULL; + obj->last_hook_id = 0; + obj->last_list_id = 0; + obj->context = xchat_get_context (ph); +} + +static void +remote_object_class_init (RemoteObjectClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = remote_object_finalize; + + signals[SERVER_SIGNAL] = + g_signal_new ("server_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + 4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); + + signals[COMMAND_SIGNAL] = + g_signal_new ("command_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + 4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); + + signals[PRINT_SIGNAL] = + g_signal_new ("print_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + 3, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); + + signals[UNLOAD_SIGNAL] = + g_signal_new ("unload_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +/* Implementation of services */ + +static gboolean +remote_object_connect (RemoteObject *obj, + const char *filename, + const char *name, + const char *desc, + const char *version, + DBusGMethodInvocation *context) +{ + static guint count = 0; + char *sender, *path; + RemoteObject *remote_object; + + sender = dbus_g_method_get_sender (context); + remote_object = g_hash_table_lookup (clients, sender); + if (remote_object != NULL) { + dbus_g_method_return (context, remote_object->dbus_path); + g_free (sender); + return TRUE; + } + path = g_strdup_printf (DBUS_OBJECT_PATH"/%d", count++); + remote_object = g_object_new (REMOTE_TYPE_OBJECT, NULL); + remote_object->dbus_path = path; + remote_object->filename = g_path_get_basename (filename); + remote_object->handle = xchat_plugingui_add (ph, + remote_object->filename, + name, + desc, + version, NULL); + dbus_g_connection_register_g_object (connection, + path, + G_OBJECT (remote_object)); + + g_hash_table_insert (clients, + sender, + remote_object); + + dbus_g_method_return (context, path); + return TRUE; +} + +static gboolean +remote_object_disconnect (RemoteObject *obj, + DBusGMethodInvocation *context) +{ + char *sender; + + sender = dbus_g_method_get_sender (context); + g_hash_table_remove (clients, sender); + g_free (sender); + + dbus_g_method_return (context); + return TRUE; +} + +static gboolean +remote_object_command (RemoteObject *obj, + const char *command, + GError **error) +{ + if (xchat_set_context (ph, obj->context)) { + xchat_command (ph, command); + } + return TRUE; +} + +static gboolean +remote_object_print (RemoteObject *obj, + const char *text, + GError **error) +{ + if (xchat_set_context (ph, obj->context)) { + xchat_print (ph, text); + } + return TRUE; +} + +static gboolean +remote_object_find_context (RemoteObject *obj, + const char *server, + const char *channel, + guint *ret_id, + GError **error) +{ + xchat_context *context; + + if (*server == '\0') { + server = NULL; + } + if (*channel == '\0') { + channel = NULL; + } + context = xchat_find_context (ph, server, channel); + *ret_id = context_list_find_id (context); + + return TRUE; +} + +static gboolean +remote_object_get_context (RemoteObject *obj, + guint *ret_id, + GError **error) +{ + *ret_id = context_list_find_id (obj->context); + return TRUE; +} + +static gboolean +remote_object_set_context (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error) +{ + xchat_context *context; + + context = context_list_find_context (id); + if (context == NULL) { + *ret = FALSE; + return TRUE; + } + obj->context = context; + *ret = TRUE; + + return TRUE; +} + +static gboolean +remote_object_get_info (RemoteObject *obj, + const char *id, + char **ret_info, + GError **error) +{ + /* win_ptr is a GtkWindow* casted to char* and will crash + * D-Bus if we send it as a string */ + if (!xchat_set_context (ph, obj->context) || + g_str_equal (id, "win_ptr")) { + *ret_info = NULL; + return TRUE; + } + *ret_info = g_strdup (xchat_get_info (ph, id)); + return TRUE; +} + +static gboolean +remote_object_get_prefs (RemoteObject *obj, + const char *name, + int *ret_type, + char **ret_str, + int *ret_int, + GError **error) +{ + const char *str; + + if (!xchat_set_context (ph, obj->context)) { + *ret_type = 0; + return TRUE; + } + *ret_type = xchat_get_prefs (ph, name, &str, ret_int); + *ret_str = g_strdup (str); + + return TRUE; +} + +static int +server_hook_cb (char *word[], + char *word_eol[], + void *userdata) +{ + HookInfo *info = (HookInfo*)userdata; + char **arg1; + char **arg2; + + arg1 = build_list (word + 1); + arg2 = build_list (word_eol + 1); + info->obj->context = xchat_get_context (ph); + g_signal_emit (info->obj, + signals[SERVER_SIGNAL], + 0, + arg1, arg2, info->id, + context_list_find_id (info->obj->context)); + g_strfreev (arg1); + g_strfreev (arg2); + + return info->return_value; +} + +static int +command_hook_cb (char *word[], + char *word_eol[], + void *userdata) +{ + HookInfo *info = (HookInfo*)userdata; + char **arg1; + char **arg2; + + arg1 = build_list (word + 1); + arg2 = build_list (word_eol + 1); + info->obj->context = xchat_get_context (ph); + g_signal_emit (info->obj, + signals[COMMAND_SIGNAL], + 0, + arg1, arg2, info->id, + context_list_find_id (info->obj->context)); + g_strfreev (arg1); + g_strfreev (arg2); + + return info->return_value; +} + +static int +print_hook_cb (char *word[], + void *userdata) +{ + HookInfo *info = (HookInfo*)userdata; + char **arg1; + + arg1 = build_list (word + 1); + info->obj->context = xchat_get_context (ph); + g_signal_emit (info->obj, + signals[PRINT_SIGNAL], + 0, + arg1, info->id, + context_list_find_id (info->obj->context)); + g_strfreev (arg1); + + return info->return_value; +} + +static gboolean +remote_object_hook_command (RemoteObject *obj, + const char *name, + int priority, + const char *help_text, + int return_value, + guint *ret_id, + GError **error) +{ + HookInfo *info; + + info = g_new0 (HookInfo, 1); + info->obj = obj; + info->return_value = return_value; + info->id = ++obj->last_hook_id; + info->hook = xchat_hook_command (ph, + name, + priority, + command_hook_cb, + help_text, + info); + g_hash_table_insert (obj->hooks, &info->id, info); + *ret_id = info->id; + + return TRUE; +} + +static gboolean +remote_object_hook_server (RemoteObject *obj, + const char *name, + int priority, + int return_value, + guint *ret_id, + GError **error) +{ + HookInfo *info; + + info = g_new0 (HookInfo, 1); + info->obj = obj; + info->return_value = return_value; + info->id = ++obj->last_hook_id; + info->hook = xchat_hook_server (ph, + name, + priority, + server_hook_cb, + info); + g_hash_table_insert (obj->hooks, &info->id, info); + *ret_id = info->id; + + return TRUE; +} + +static gboolean +remote_object_hook_print (RemoteObject *obj, + const char *name, + int priority, + int return_value, + guint *ret_id, + GError **error) +{ + HookInfo *info; + + info = g_new0 (HookInfo, 1); + info->obj = obj; + info->return_value = return_value; + info->id = ++obj->last_hook_id; + info->hook = xchat_hook_print (ph, + name, + priority, + print_hook_cb, + info); + g_hash_table_insert (obj->hooks, &info->id, info); + *ret_id = info->id; + + return TRUE; +} + +static gboolean +remote_object_unhook (RemoteObject *obj, + guint id, + GError **error) +{ + g_hash_table_remove (obj->hooks, &id); + return TRUE; +} + +static gboolean +remote_object_list_get (RemoteObject *obj, + const char *name, + guint *ret_id, + GError **error) +{ + xchat_list *xlist; + guint *id; + + if (!xchat_set_context (ph, obj->context)) { + *ret_id = 0; + return TRUE; + } + xlist = xchat_list_get (ph, name); + if (xlist == NULL) { + *ret_id = 0; + return TRUE; + } + id = g_new (guint, 1); + *id = ++obj->last_list_id; + *ret_id = *id; + g_hash_table_insert (obj->lists, + id, + xlist); + + return TRUE; +} + +static gboolean +remote_object_list_next (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL) { + *ret = FALSE; + return TRUE; + } + *ret = xchat_list_next (ph, xlist); + + return TRUE; +} + +static gboolean +remote_object_list_str (RemoteObject *obj, + guint id, + const char *name, + char **ret_str, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL && !xchat_set_context (ph, obj->context)) { + *ret_str = NULL; + return TRUE; + } + if (g_str_equal (name, "context")) { + *ret_str = NULL; + return TRUE; + } + *ret_str = g_strdup (xchat_list_str (ph, xlist, name)); + + return TRUE; +} + +static gboolean +remote_object_list_int (RemoteObject *obj, + guint id, + const char *name, + int *ret_int, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL && !xchat_set_context (ph, obj->context)) { + *ret_int = -1; + return TRUE; + } + if (g_str_equal (name, "context")) { + xchat_context *context; + context = (xchat_context*)xchat_list_str (ph, xlist, name); + *ret_int = context_list_find_id (context); + } else { + *ret_int = xchat_list_int (ph, xlist, name); + } + + return TRUE; +} + +static gboolean +remote_object_list_time (RemoteObject *obj, + guint id, + const char *name, + guint64 *ret_time, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL) { + *ret_time = (guint64) -1; + return TRUE; + } + *ret_time = xchat_list_time (ph, xlist, name); + + return TRUE; +} + +static gboolean +remote_object_list_fields (RemoteObject *obj, + const char *name, + char ***ret, + GError **error) +{ + *ret = g_strdupv ((char**)xchat_list_fields (ph, name)); + if (*ret == NULL) { + *ret = g_new0 (char*, 1); + } + return TRUE; +} + +static gboolean +remote_object_list_free (RemoteObject *obj, + guint id, + GError **error) +{ + g_hash_table_remove (obj->lists, &id); + return TRUE; +} + +static gboolean +remote_object_emit_print (RemoteObject *obj, + const char *event_name, + const char *args[], + gboolean *ret, + GError **error) +{ + const char *argv[4] = {NULL, NULL, NULL, NULL}; + int i; + + for (i = 0; i < 4 && args[i] != NULL; i++) { + argv[i] = args[i]; + } + + *ret = xchat_set_context (ph, obj->context); + if (*ret) { + *ret = xchat_emit_print (ph, event_name, argv[0], argv[1], + argv[2], argv[3]); + } + + return TRUE; +} + +static gboolean +remote_object_nickcmp (RemoteObject *obj, + const char *nick1, + const char *nick2, + int *ret, + GError **error) +{ + xchat_set_context (ph, obj->context); + *ret = xchat_nickcmp (ph, nick1, nick2); + return TRUE; +} + +static gboolean +remote_object_strip (RemoteObject *obj, + const char *str, + int len, + int flag, + char **ret_str, + GError **error) +{ + *ret_str = xchat_strip (ph, str, len, flag); + return TRUE; +} + +static gboolean +remote_object_send_modes (RemoteObject *obj, + const char *targets[], + int modes_per_line, + char sign, + char mode, + GError **error) +{ + if (xchat_set_context (ph, obj->context)) { + xchat_send_modes (ph, targets, + g_strv_length ((char**)targets), + modes_per_line, + sign, mode); + } + return TRUE; +} + +/* DBUS stuffs */ + +static void +name_owner_changed (DBusGProxy *driver_proxy, + const char *name, + const char *old_owner, + const char *new_owner, + void *user_data) +{ + if (*new_owner == '\0') { + /* this name has vanished */ + g_hash_table_remove (clients, name); + } +} + +static gboolean +init_dbus (void) +{ + DBusGProxy *proxy; + RemoteObject *remote; + guint request_name_result; + GError *error = NULL; + + dbus_g_object_type_install_info (REMOTE_TYPE_OBJECT, + &dbus_glib_remote_object_object_info); + + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (connection == NULL) { + xchat_printf (ph, _("Couldn't connect to session bus: %s\n"), + error->message); + g_error_free (error); + return FALSE; + } + + proxy = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + + if (!dbus_g_proxy_call (proxy, "RequestName", &error, + G_TYPE_STRING, DBUS_SERVICE, + G_TYPE_UINT, DBUS_NAME_FLAG_ALLOW_REPLACEMENT, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) { + xchat_printf (ph, _("Failed to acquire %s: %s\n"), + DBUS_SERVICE, + error->message); + g_error_free (error); + + return FALSE; + } + + dbus_g_proxy_add_signal (proxy, "NameOwnerChanged", + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (proxy, "NameOwnerChanged", + G_CALLBACK (name_owner_changed), + NULL, NULL); + + remote = g_object_new (REMOTE_TYPE_OBJECT, NULL); + dbus_g_connection_register_g_object (connection, + DBUS_OBJECT_PATH"/Remote", + G_OBJECT (remote)); + + return TRUE; +} + +/* xchat_plugin stuffs */ + +static char** +build_list (char *word[]) +{ + guint i; + guint num = 0; + char **result; + + if (word == NULL) { + return NULL; + } + + while (word[num] && word[num][0]) { + num++; + } + + result = g_new0 (char*, num + 1); + for (i = 0; i < num; i++) { + result[i] = g_strdup (word[i]); + } + + return result; +} + +static guint +context_list_find_id (xchat_context *context) +{ + GList *l = NULL; + + for (l = contexts; l != NULL; l = l->next) { + if (((ContextInfo*)l->data)->context == context) { + return ((ContextInfo*)l->data)->id; + } + } + + return 0; +} + +static xchat_context* +context_list_find_context (guint id) +{ + GList *l = NULL; + + for (l = contexts; l != NULL; l = l->next) { + if (((ContextInfo*)l->data)->id == id) { + return ((ContextInfo*)l->data)->context; + } + } + + return NULL; +} + +static int +open_context_cb (char *word[], + void *userdata) +{ + ContextInfo *info; + + info = g_new0 (ContextInfo, 1); + info->id = ++last_context_id; + info->context = xchat_get_context (ph); + contexts = g_list_prepend (contexts, info); + + return XCHAT_EAT_NONE; +} + +static int +close_context_cb (char *word[], + void *userdata) +{ + GList *l; + xchat_context *context = xchat_get_context (ph); + + for (l = contexts; l != NULL; l = l->next) { + if (((ContextInfo*)l->data)->context == context) { + g_free (l->data); + contexts = g_list_delete_link (contexts, l); + break; + } + } + + return XCHAT_EAT_NONE; +} + +static gboolean +clients_find_filename_foreach (gpointer key, + gpointer value, + gpointer user_data) +{ + RemoteObject *obj = REMOTE_OBJECT (value); + return g_str_equal (obj->filename, (char*)user_data); +} + +static int +unload_plugin_cb (char *word[], char *word_eol[], void *userdata) +{ + RemoteObject *obj; + + obj = g_hash_table_find (clients, + clients_find_filename_foreach, + word[2]); + if (obj != NULL) { + g_signal_emit (obj, + signals[UNLOAD_SIGNAL], + 0); + return XCHAT_EAT_ALL; + } + + return XCHAT_EAT_NONE; +} + +int +dbus_plugin_init (xchat_plugin *plugin_handle, + char **plugin_name, + char **plugin_desc, + char **plugin_version, + char *arg) +{ + ph = plugin_handle; + *plugin_name = PNAME; + *plugin_desc = PDESC; + *plugin_version = PVERSION; + + if (init_dbus()) { + /*xchat_printf (ph, _("%s loaded successfully!\n"), PNAME);*/ + + clients = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + xchat_hook_print (ph, "Open Context", + XCHAT_PRI_NORM, + open_context_cb, + NULL); + + xchat_hook_print (ph, "Close Context", + XCHAT_PRI_NORM, + close_context_cb, + NULL); + + xchat_hook_command (ph, "unload", + XCHAT_PRI_HIGHEST, + unload_plugin_cb, NULL, NULL); + } + + return TRUE; +} diff --git a/src/common/dbus/dbus-plugin.h b/src/common/dbus/dbus-plugin.h new file mode 100644 index 00000000..bda8f61c --- /dev/null +++ b/src/common/dbus/dbus-plugin.h @@ -0,0 +1,31 @@ +/* dbus-plugin.c - xchat plugin for remote access using DBUS + * Copyright (C) 2006 Claessens Xavier + * + * 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 + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#ifndef XCHAT_DBUS_PLUGIN_H +#define XCHAT_DBUS_PLUGIN_H + +int dbus_plugin_init (xchat_plugin *plugin_handle, + char **plugin_name, + char **plugin_desc, + char **plugin_version, + char *arg); + +#endif diff --git a/src/common/dbus/example.c b/src/common/dbus/example.c new file mode 100644 index 00000000..1d072785 --- /dev/null +++ b/src/common/dbus/example.c @@ -0,0 +1,201 @@ +/* example.c - program to demonstrate some D-BUS stuffs. + * Copyright (C) 2006 Claessens Xavier + * + * 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. + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#include <config.h> +#include <dbus/dbus-glib.h> +#include <stdlib.h> +#include "marshallers.h" + +#define DBUS_SERVICE "org.xchat.service" +#define DBUS_REMOTE "/org/xchat/Remote" +#define DBUS_REMOTE_CONNECTION_INTERFACE "org.xchat.connection" +#define DBUS_REMOTE_PLUGIN_INTERFACE "org.xchat.plugin" + +guint command_id; +guint server_id; + +static void +write_error (char *message, + GError **error) +{ + if (error == NULL || *error == NULL) { + return; + } + g_printerr ("%s: %s\n", message, (*error)->message); + g_clear_error (error); +} + +static void +test_server_cb (DBusGProxy *proxy, + char *word[], + char *word_eol[], + guint hook_id, + guint context_id, + gpointer user_data) +{ + if (hook_id == server_id) { + g_print ("message: %s\n", word_eol[0]); + } +} + +static void +test_command_cb (DBusGProxy *proxy, + char *word[], + char *word_eol[], + guint hook_id, + guint context_id, + gpointer user_data) +{ + GError *error = NULL; + + if (hook_id == command_id) { + if (!dbus_g_proxy_call (proxy, "Unhook", + &error, + G_TYPE_UINT, hook_id, + G_TYPE_INVALID, G_TYPE_INVALID)) { + write_error ("Failed to complete unhook", &error); + } + /* Now if you write "/test blah" again in the xchat window + * you'll get a "Unknown command" error message */ + g_print ("test command received: %s\n", word_eol[1]); + if (!dbus_g_proxy_call (proxy, "Print", + &error, + G_TYPE_STRING, "test command succeed", + G_TYPE_INVALID, + G_TYPE_INVALID)) { + write_error ("Failed to complete Print", &error); + } + } +} + +static void +unload_cb (void) +{ + g_print ("Good bye !\n"); + exit (EXIT_SUCCESS); +} + +int +main (int argc, char **argv) +{ + DBusGConnection *connection; + DBusGProxy *remote_object; + GMainLoop *mainloop; + gchar *path; + GError *error = NULL; + + g_type_init (); + + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (connection == NULL) { + write_error ("Couldn't connect to session bus", &error); + return EXIT_FAILURE; + } + + remote_object = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE, + DBUS_REMOTE, + DBUS_REMOTE_CONNECTION_INTERFACE); + if (!dbus_g_proxy_call (remote_object, "Connect", + &error, + G_TYPE_STRING, argv[0], + G_TYPE_STRING, "example", + G_TYPE_STRING, "Example of a D-Bus client", + G_TYPE_STRING, "1.0", + G_TYPE_INVALID, + G_TYPE_STRING, &path, G_TYPE_INVALID)) { + write_error ("Failed to complete Connect", &error); + return EXIT_FAILURE; + } + g_object_unref (remote_object); + + remote_object = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE, + path, + DBUS_REMOTE_PLUGIN_INTERFACE); + g_free (path); + + if (!dbus_g_proxy_call (remote_object, "HookCommand", + &error, + G_TYPE_STRING, "test", + G_TYPE_INT, 0, + G_TYPE_STRING, "Simple D-BUS example", + G_TYPE_INT, 1, G_TYPE_INVALID, + G_TYPE_UINT, &command_id, G_TYPE_INVALID)) { + write_error ("Failed to complete HookCommand", &error); + return EXIT_FAILURE; + } + g_print ("Command hook id=%d\n", command_id); + + if (!dbus_g_proxy_call (remote_object, "HookServer", + &error, + G_TYPE_STRING, "RAW LINE", + G_TYPE_INT, 0, + G_TYPE_INT, 0, G_TYPE_INVALID, + G_TYPE_UINT, &server_id, G_TYPE_INVALID)) { + write_error ("Failed to complete HookServer", &error); + return EXIT_FAILURE; + } + g_print ("Server hook id=%d\n", server_id); + + dbus_g_object_register_marshaller ( + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT, + G_TYPE_INVALID); + + dbus_g_object_register_marshaller ( + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + G_TYPE_INVALID); + + dbus_g_proxy_add_signal (remote_object, "CommandSignal", + G_TYPE_STRV, + G_TYPE_STRV, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (remote_object, "CommandSignal", + G_CALLBACK (test_command_cb), + NULL, NULL); + + dbus_g_proxy_add_signal (remote_object, "ServerSignal", + G_TYPE_STRV, + G_TYPE_STRV, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (remote_object, "ServerSignal", + G_CALLBACK (test_server_cb), + NULL, NULL); + + dbus_g_proxy_add_signal (remote_object, "UnloadSignal", + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (remote_object, "UnloadSignal", + G_CALLBACK (unload_cb), + NULL, NULL); + + /* Now you can write on the xchat windows: "/test arg1 arg2 ..." */ + mainloop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (mainloop); + + return EXIT_SUCCESS; +} diff --git a/src/common/dbus/example.py b/src/common/dbus/example.py new file mode 100644 index 00000000..08bfdac3 --- /dev/null +++ b/src/common/dbus/example.py @@ -0,0 +1,28 @@ +#! /usr/bin/python + +import dbus + +bus = dbus.SessionBus() +proxy = bus.get_object('org.xchat.service', '/org/xchat/Remote') +remote = dbus.Interface(proxy, 'org.xchat.connection') +path = remote.Connect ("example.py", + "Python example", + "Example of a D-Bus client written in python", + "1.0") +proxy = bus.get_object('org.xchat.service', path) +xchat = dbus.Interface(proxy, 'org.xchat.plugin') + +channels = xchat.ListGet ("channels") +while xchat.ListNext (channels): + name = xchat.ListStr (channels, "channel") + print "------- " + name + " -------" + xchat.SetContext (xchat.ListInt (channels, "context")) + xchat.EmitPrint ("Channel Message", ["John", "Hi there", "@"]) + users = xchat.ListGet ("users") + while xchat.ListNext (users): + print "Nick: " + xchat.ListStr (users, "nick") + xchat.ListFree (users) +xchat.ListFree (channels) + +print xchat.Strip ("\00312Blue\003 \002Bold!\002", -1, 1|2) + diff --git a/src/common/dbus/marshallers.list b/src/common/dbus/marshallers.list new file mode 100644 index 00000000..bc3c4ad5 --- /dev/null +++ b/src/common/dbus/marshallers.list @@ -0,0 +1 @@ +VOID:POINTER,POINTER,UINT,UINT diff --git a/src/common/dbus/org.xchat.service.service.in b/src/common/dbus/org.xchat.service.service.in new file mode 100644 index 00000000..19490121 --- /dev/null +++ b/src/common/dbus/org.xchat.service.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.xchat.service +Exec=@bindir@/xchat diff --git a/src/common/dbus/remote-object.xml b/src/common/dbus/remote-object.xml new file mode 100644 index 00000000..88a8ae7c --- /dev/null +++ b/src/common/dbus/remote-object.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/"> + + <interface name="org.xchat.connection"> + <method name="Connect"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg type="s" name="filename" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="s" name="desc" direction="in"/> + <arg type="s" name="version" direction="in"/> + <arg type="s" name="path" direction="out"/> + </method> + <method name="Disconnect"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + </method> + </interface> + + <interface name="org.xchat.plugin"> + <method name="Command"> + <arg type="s" name="command" direction="in"/> + </method> + <method name="Print"> + <arg type="s" name="text" direction="in"/> + </method> + <method name="FindContext"> + <arg type="s" name="server" direction="in"/> + <arg type="s" name="channel" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="GetContext"> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="SetContext"> + <arg type="u" name="id" direction="in"/> + <arg type="b" name="ret" direction="out"/> + </method> + <method name="GetInfo"> + <arg type="s" name="id" direction="in"/> + <arg type="s" name="ret_info" direction="out"/> + </method> + <method name="GetPrefs"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="ret_type" direction="out"/> + <arg type="s" name="ret_str" direction="out"/> + <arg type="i" name="ret_int" direction="out"/> + </method> + <method name="HookCommand"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="priority" direction="in"/> + <arg type="s" name="help_text" direction="in"/> + <arg type="i" name="return_value" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="HookServer"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="priority" direction="in"/> + <arg type="i" name="return_value" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="HookPrint"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="priority" direction="in"/> + <arg type="i" name="return_value" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="Unhook"> + <arg type="u" name="id" direction="in"/> + </method> + <method name="ListGet"> + <arg type="s" name="name" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="ListNext"> + <arg type="u" name="id" direction="in"/> + <arg type="b" name="ret" direction="out"/> + </method> + <method name="ListStr"> + <arg type="u" name="id" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="s" name="ret_str" direction="out"/> + </method> + <method name="ListInt"> + <arg type="u" name="id" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="ret_int" direction="out"/> + </method> + <method name="ListTime"> + <arg type="u" name="id" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="t" name="ret_time" direction="out"/> + </method> + <method name="ListFields"> + <arg type="s" name="name" direction="in"/> + <arg type="as" name="ret" direction="out"/> + </method> + <method name="ListFree"> + <arg type="u" name="id" direction="in"/> + </method> + <method name="EmitPrint"> + <arg type="s" name="event_name" direction="in"/> + <arg type="as" name="args" direction="in"/> + <arg type="b" name="ret" direction="out"/> + </method> + <method name="Nickcmp"> + <arg type="s" name="nick1" direction="in"/> + <arg type="s" name="nick2" direction="in"/> + <arg type="i" name="ret" direction="out"/> + </method> + <method name="Strip"> + <arg type="s" name="str" direction="in"/> + <arg type="i" name="len" direction="in"/> + <arg type="i" name="flag" direction="in"/> + <arg type="s" name="ret_str" direction="out"/> + </method> + <method name="SendModes"> + <arg type="as" name="targets" direction="in"/> + <arg type="i" name="modes_per_line" direction="in"/> + <arg type="y" name="sign" direction="in"/> + <arg type="y" name="mode" direction="in"/> + </method> + + <signal name="CommandSignal"> + <arg type="as" name="word"/> + <arg type="as" name="word_eol"/> + <arg type="u" name="hook_id"/> + <arg type="u" name="context_id"/> + </signal> + <signal name="ServerSignal"> + <arg type="as" name="word"/> + <arg type="as" name="word_eol"/> + <arg type="u" name="hook_id"/> + <arg type="u" name="context_id"/> + </signal> + <signal name="PrintSignal"> + <arg type="as" name="word"/> + <arg type="u" name="hook_id"/> + <arg type="u" name="context_id"/> + </signal> + <signal name="UnloadSignal"/> + </interface> +</node> diff --git a/src/common/dcc.c b/src/common/dcc.c new file mode 100644 index 00000000..8f289342 --- /dev/null +++ b/src/common/dcc.c @@ -0,0 +1,2587 @@ +/* X-Chat + * Copyright (C) 1998-2006 Peter Zelezny. + * Copyright (C) 2006 Damjan Jovanovic + * + * 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 + * + * Wayne Conrad, 3 Apr 1999: Color-coded DCC file transfer status windows + * Bernhard Valenti <bernhard.valenti@gmx.net> 2000-11-21: Fixed DCC send behind nat + * + * 2001-03-08 Added support for getting "dcc_ip" config parameter. + * Jim Seymour (jseymour@LinxNet.com) + */ + +/* we only use 32 bits, but without this define, you get only 31! */ +#define _FILE_OFFSET_BITS 64 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#ifdef WIN32 +#include <windows.h> +#endif + +#include "xchat.h" +#include "util.h" +#include "fe.h" +#include "outbound.h" +#include "inbound.h" +#include "network.h" +#include "plugin.h" +#include "server.h" +#include "text.h" +#include "url.h" +#include "xchatc.h" + +#ifdef USE_DCC64 +#define BIG_STR_TO_INT(x) strtoull(x,NULL,10) +#else +#define BIG_STR_TO_INT(x) strtoul(x,NULL,10) +#endif + +static char *dcctypes[] = { "SEND", "RECV", "CHAT", "CHAT" }; + +struct dccstat_info dccstat[] = { + {N_("Waiting"), 1 /*black */ }, + {N_("Active"), 12 /*cyan */ }, + {N_("Failed"), 4 /*red */ }, + {N_("Done"), 3 /*green */ }, + {N_("Connect"), 1 /*black */ }, + {N_("Aborted"), 4 /*red */ }, +}; + +static int dcc_global_throttle; /* 0x1 = sends, 0x2 = gets */ +/*static*/ int dcc_sendcpssum, dcc_getcpssum; + +static struct DCC *new_dcc (void); +static void dcc_close (struct DCC *dcc, int dccstat, int destroy); +static gboolean dcc_send_data (GIOChannel *, GIOCondition, struct DCC *); +static gboolean dcc_read (GIOChannel *, GIOCondition, struct DCC *); +static gboolean dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc); + +static int new_id() +{ + static int id = 0; + if (id == 0) + { + /* start the first ID at a random number for pseudo security */ + /* 1 - 255 */ + id = RAND_INT(255) + 1; + /* ignore overflows, since it can go to 2 billion */ + } + return id++; +} + +static double +timeval_diff (GTimeVal *greater, + GTimeVal *less) +{ + long usecdiff; + double result; + + result = greater->tv_sec - less->tv_sec; + usecdiff = (long) greater->tv_usec - less->tv_usec; + result += (double) usecdiff / 1000000; + + return result; +} + +static void +dcc_unthrottle (struct DCC *dcc) +{ + /* don't unthrottle here, but delegate to funcs */ + if (dcc->type == TYPE_RECV) + dcc_read (NULL, 0, dcc); + else + dcc_send_data (NULL, 0, dcc); +} + +static void +dcc_calc_cps (struct DCC *dcc) +{ + GTimeVal now; + int oldcps; + double timediff, startdiff; + int glob_throttle_bit, wasthrottled; + int *cpssum, glob_limit; + DCC_SIZE pos, posdiff; + + g_get_current_time (&now); + + /* the pos we use for sends is an average + between pos and ack */ + if (dcc->type == TYPE_SEND) + { + /* carefull to avoid 32bit overflow */ + pos = dcc->pos - ((dcc->pos - dcc->ack) / 2); + glob_throttle_bit = 0x1; + cpssum = &dcc_sendcpssum; + glob_limit = prefs.dcc_global_max_send_cps; + } + else + { + pos = dcc->pos; + glob_throttle_bit = 0x2; + cpssum = &dcc_getcpssum; + glob_limit = prefs.dcc_global_max_get_cps; + } + + if (!dcc->firstcpstv.tv_sec && !dcc->firstcpstv.tv_usec) + dcc->firstcpstv = now; + else + { + startdiff = timeval_diff (&now, &dcc->firstcpstv); + if (startdiff < 1) + startdiff = 1; + else if (startdiff > CPS_AVG_WINDOW) + startdiff = CPS_AVG_WINDOW; + + timediff = timeval_diff (&now, &dcc->lastcpstv); + if (timediff > startdiff) + timediff = startdiff = 1; + + posdiff = pos - dcc->lastcpspos; + oldcps = dcc->cps; + dcc->cps = ((double) posdiff / timediff) * (timediff / startdiff) + + (double) dcc->cps * (1.0 - (timediff / startdiff)); + + *cpssum += dcc->cps - oldcps; + } + + dcc->lastcpspos = pos; + dcc->lastcpstv = now; + + /* now check cps against set limits... */ + wasthrottled = dcc->throttled; + + /* check global limits first */ + dcc->throttled &= ~0x2; + if (glob_limit > 0 && *cpssum >= glob_limit) + { + dcc_global_throttle |= glob_throttle_bit; + if (dcc->maxcps >= 0) + dcc->throttled |= 0x2; + } + else + dcc_global_throttle &= ~glob_throttle_bit; + + /* now check per-connection limit */ + if (dcc->maxcps > 0 && dcc->cps > dcc->maxcps) + dcc->throttled |= 0x1; + else + dcc->throttled &= ~0x1; + + /* take action */ + if (wasthrottled && !dcc->throttled) + dcc_unthrottle (dcc); +} + +static void +dcc_remove_from_sum (struct DCC *dcc) +{ + if (dcc->dccstat != STAT_ACTIVE) + return; + if (dcc->type == TYPE_SEND) + dcc_sendcpssum -= dcc->cps; + else if (dcc->type == TYPE_RECV) + dcc_getcpssum -= dcc->cps; +} + +gboolean +is_dcc (struct DCC *dcc) +{ + GSList *list = dcc_list; + while (list) + { + if (list->data == dcc) + return TRUE; + list = list->next; + } + return FALSE; +} + +/* this is called from xchat.c:xchat_misc_checks() every 1 second. */ + +void +dcc_check_timeouts (void) +{ + struct DCC *dcc; + time_t tim = time (0); + GSList *next, *list = dcc_list; + + while (list) + { + dcc = (struct DCC *) list->data; + next = list->next; + + switch (dcc->dccstat) + { + case STAT_ACTIVE: + dcc_calc_cps (dcc); + fe_dcc_update (dcc); + + if (dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) + { + if (prefs.dccstalltimeout > 0) + { + if (!dcc->throttled + && tim - dcc->lasttime > prefs.dccstalltimeout) + { + EMIT_SIGNAL (XP_TE_DCCSTALL, dcc->serv->front_session, + dcctypes[dcc->type], + file_part (dcc->file), dcc->nick, NULL, 0); + dcc_close (dcc, STAT_ABORTED, FALSE); + } + } + } + break; + case STAT_QUEUED: + if (dcc->type == TYPE_SEND || dcc->type == TYPE_CHATSEND) + { + if (tim - dcc->offertime > prefs.dcctimeout) + { + if (prefs.dcctimeout > 0) + { + EMIT_SIGNAL (XP_TE_DCCTOUT, dcc->serv->front_session, + dcctypes[dcc->type], + file_part (dcc->file), dcc->nick, NULL, 0); + dcc_close (dcc, STAT_ABORTED, FALSE); + } + } + } + break; + case STAT_DONE: + case STAT_FAILED: + case STAT_ABORTED: + if (prefs.dcc_remove) + dcc_close (dcc, 0, TRUE); + break; + } + list = next; + } +} + +static int +dcc_lookup_proxy (char *host, struct sockaddr_in *addr) +{ + struct hostent *h; + static char *cache_host = NULL; + static guint32 cache_addr; + + /* too lazy to thread this, so we cache results */ + if (cache_host) + { + if (strcmp (host, cache_host) == 0) + { + memcpy (&addr->sin_addr, &cache_addr, 4); + return TRUE; + } + free (cache_host); + cache_host = NULL; + } + + h = gethostbyname (host); + if (h != NULL && h->h_length == 4 && h->h_addr_list[0] != NULL) + { + memcpy (&addr->sin_addr, h->h_addr, 4); + memcpy (&cache_addr, h->h_addr, 4); + cache_host = strdup (host); + return TRUE; + } + + return FALSE; +} + +#define DCC_USE_PROXY() (prefs.proxy_host[0] && prefs.proxy_type>0 && prefs.proxy_type<5 && prefs.proxy_use!=1) + +static int +dcc_connect_sok (struct DCC *dcc) +{ + int sok; + struct sockaddr_in addr; + + sok = socket (AF_INET, SOCK_STREAM, 0); + if (sok == -1) + return -1; + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + if (DCC_USE_PROXY ()) + { + if (!dcc_lookup_proxy (prefs.proxy_host, &addr)) + { + closesocket (sok); + return -1; + } + addr.sin_port = htons (prefs.proxy_port); + } + else + { + addr.sin_port = htons (dcc->port); + addr.sin_addr.s_addr = htonl (dcc->addr); + } + + set_nonblocking (sok); + connect (sok, (struct sockaddr *) &addr, sizeof (addr)); + + return sok; +} + +static void +dcc_close (struct DCC *dcc, int dccstat, int destroy) +{ + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + + if (dcc->sok != -1) + { + closesocket (dcc->sok); + dcc->sok = -1; + } + + dcc_remove_from_sum (dcc); + + if (dcc->fp != -1) + { + close (dcc->fp); + dcc->fp = -1; + + if(dccstat == STAT_DONE) + { + /* if we just completed a dcc recieve, move the */ + /* completed file to the completed directory */ + if(dcc->type == TYPE_RECV) + { + /* mgl: change this to use destfile_fs for correctness and to */ + /* handle the case where dccwithnick is set */ + move_file_utf8 (prefs.dccdir, prefs.dcc_completed_dir, + file_part (dcc->destfile), prefs.dccpermissions); + } + + } + } + + dcc->dccstat = dccstat; + if (dcc->dccchat) + { + free (dcc->dccchat); + dcc->dccchat = NULL; + } + + if (destroy) + { + dcc_list = g_slist_remove (dcc_list, dcc); + fe_dcc_remove (dcc); + if (dcc->proxy) + free (dcc->proxy); + if (dcc->file) + free (dcc->file); + if (dcc->destfile) + g_free (dcc->destfile); + if (dcc->destfile_fs) + g_free (dcc->destfile_fs); + free (dcc->nick); + free (dcc); + return; + } + + fe_dcc_update (dcc); +} + +void +dcc_abort (session *sess, struct DCC *dcc) +{ + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_QUEUED: + case STAT_CONNECTING: + case STAT_ACTIVE: + dcc_close (dcc, STAT_ABORTED, FALSE); + switch (dcc->type) + { + case TYPE_CHATSEND: + case TYPE_CHATRECV: + EMIT_SIGNAL (XP_TE_DCCCHATABORT, sess, dcc->nick, NULL, NULL, + NULL, 0); + break; + case TYPE_SEND: + EMIT_SIGNAL (XP_TE_DCCSENDABORT, sess, dcc->nick, + file_part (dcc->file), NULL, NULL, 0); + break; + case TYPE_RECV: + EMIT_SIGNAL (XP_TE_DCCRECVABORT, sess, dcc->nick, + dcc->file, NULL, NULL, 0); + } + break; + default: + dcc_close (dcc, 0, TRUE); + } + } +} + +void +dcc_notify_kill (struct server *serv) +{ + struct server *replaceserv = 0; + struct DCC *dcc; + GSList *list = dcc_list; + if (serv_list) + replaceserv = (struct server *) serv_list->data; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->serv == serv) + dcc->serv = replaceserv; + list = list->next; + } +} + +struct DCC * +dcc_write_chat (char *nick, char *text) +{ + struct DCC *dcc; + int len; + + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (!dcc) + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc && dcc->dccstat == STAT_ACTIVE) + { + len = strlen (text); + tcp_send_real (NULL, dcc->sok, dcc->serv->encoding, dcc->serv->using_irc, + text, len); + send (dcc->sok, "\n", 1, 0); + dcc->size += len; + fe_dcc_update (dcc); + return dcc; + } + return 0; +} + +/* returns: 0 - ok + 1 - the dcc is closed! */ + +static int +dcc_chat_line (struct DCC *dcc, char *line) +{ + session *sess; + char *word[PDIWORDS]; + char *po; + char *utf; + char *conv; + int ret, i; + int len; + gsize utf_len; + char portbuf[32]; + + len = strlen (line); + if (dcc->serv->using_cp1255) + len++; /* include the NUL terminator */ + + if (dcc->serv->using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */ + utf = NULL; + else if (dcc->serv->encoding == NULL) /* system */ + utf = g_locale_to_utf8 (line, len, NULL, &utf_len, NULL); + else + utf = g_convert (line, len, "UTF-8", dcc->serv->encoding, 0, &utf_len, 0); + + if (utf) + { + line = utf; + len = utf_len; + } + + if (dcc->serv->using_cp1255 && len > 0) + len--; + + /* we really need valid UTF-8 now */ + conv = text_validate (&line, &len); + + sess = find_dialog (dcc->serv, dcc->nick); + if (!sess) + sess = dcc->serv->front_session; + + sprintf (portbuf, "%d", dcc->port); + + word[0] = "DCC Chat Text"; + word[1] = net_ip (dcc->addr); + word[2] = portbuf; + word[3] = dcc->nick; + word[4] = line; + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + ret = plugin_emit_print (sess, word); + + /* did the plugin close it? */ + if (!g_slist_find (dcc_list, dcc)) + { + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 1; + } + + /* did the plugin eat the event? */ + if (ret) + { + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 0; + } + + url_check_line (line, len); + + if (line[0] == 1 && !strncasecmp (line + 1, "ACTION", 6)) + { + po = strchr (line + 8, '\001'); + if (po) + po[0] = 0; + inbound_action (sess, dcc->serv->nick, dcc->nick, "", line + 8, FALSE, FALSE); + } else + { + inbound_privmsg (dcc->serv, dcc->nick, "", line, FALSE); + } + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 0; +} + +static gboolean +dcc_read_chat (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int i, len, dead; + char portbuf[32]; + char lbuf[2050]; + + while (1) + { + if (dcc->throttled) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + return FALSE; + } + + if (!dcc->iotag) + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + + len = recv (dcc->sok, lbuf, sizeof (lbuf) - 2, 0); + if (len < 1) + { + if (len < 0) + { + if (would_block ()) + return TRUE; + } + sprintf (portbuf, "%d", dcc->port); + EMIT_SIGNAL (XP_TE_DCCCHATF, dcc->serv->front_session, dcc->nick, + net_ip (dcc->addr), portbuf, + errorstring ((len < 0) ? sock_error () : 0), 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + i = 0; + lbuf[len] = 0; + while (i < len) + { + switch (lbuf[i]) + { + case '\r': + break; + case '\n': + dcc->dccchat->linebuf[dcc->dccchat->pos] = 0; + dead = dcc_chat_line (dcc, dcc->dccchat->linebuf); + + if (dead || !dcc->dccchat) /* the dcc has been closed, don't use (DCC *)! */ + return TRUE; + + dcc->pos += dcc->dccchat->pos; + dcc->dccchat->pos = 0; + fe_dcc_update (dcc); + break; + default: + dcc->dccchat->linebuf[dcc->dccchat->pos] = lbuf[i]; + if (dcc->dccchat->pos < (sizeof (dcc->dccchat->linebuf) - 1)) + dcc->dccchat->pos++; + } + i++; + } + } +} + +static void +dcc_calc_average_cps (struct DCC *dcc) +{ + time_t sec; + + sec = time (0) - dcc->starttime; + if (sec < 1) + sec = 1; + if (dcc->type == TYPE_SEND) + dcc->cps = (dcc->ack - dcc->resumable) / sec; + else + dcc->cps = (dcc->pos - dcc->resumable) / sec; +} + +static void +dcc_send_ack (struct DCC *dcc) +{ + /* send in 32-bit big endian */ + guint32 pos = htonl (dcc->pos & 0xffffffff); + send (dcc->sok, (char *) &pos, 4, 0); +} + +static gboolean +dcc_read (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char *old; + char buf[4096]; + int n; + gboolean need_ack = FALSE; + + if (dcc->fp == -1) + { + + /* try to create the download dir (even if it exists, no harm) */ + mkdir_utf8 (prefs.dccdir); + + if (dcc->resumable) + { + dcc->fp = open (dcc->destfile_fs, O_WRONLY | O_APPEND | OFLAGS); + dcc->pos = dcc->resumable; + dcc->ack = dcc->resumable; + } else + { + if (access (dcc->destfile_fs, F_OK) == 0) + { + n = 0; + do + { + n++; + snprintf (buf, sizeof (buf), "%s.%d", dcc->destfile_fs, n); + } + while (access (buf, F_OK) == 0); + + g_free (dcc->destfile_fs); + dcc->destfile_fs = g_strdup (buf); + + old = dcc->destfile; + dcc->destfile = xchat_filename_to_utf8 (buf, -1, 0, 0, 0); + + EMIT_SIGNAL (XP_TE_DCCRENAME, dcc->serv->front_session, + old, dcc->destfile, NULL, NULL, 0); + g_free (old); + } + dcc->fp = + open (dcc->destfile_fs, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, + prefs.dccpermissions); + } + } + if (dcc->fp == -1) + { + /* the last executed function is open(), errno should be valid */ + EMIT_SIGNAL (XP_TE_DCCFILEERR, dcc->serv->front_session, dcc->destfile, + errorstring (errno), NULL, NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + while (1) + { + if (dcc->throttled) + { + if (need_ack) + dcc_send_ack (dcc); + + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + return FALSE; + } + + if (!dcc->iotag) + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc); + + n = recv (dcc->sok, buf, sizeof (buf), 0); + if (n < 1) + { + if (n < 0) + { + if (would_block ()) + { + if (need_ack) + dcc_send_ack (dcc); + return TRUE; + } + } + EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file, + dcc->destfile, dcc->nick, + errorstring ((n < 0) ? sock_error () : 0), 0); + /* send ack here? but the socket is dead */ + /*if (need_ack) + dcc_send_ack (dcc);*/ + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + if (write (dcc->fp, buf, n) == -1) /* could be out of hdd space */ + { + EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file, + dcc->destfile, dcc->nick, errorstring (errno), 0); + if (need_ack) + dcc_send_ack (dcc); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + dcc->lasttime = time (0); + dcc->pos += n; + need_ack = TRUE; /* send ack when we're done recv()ing */ + + if (dcc->pos >= dcc->size) + { + dcc_send_ack (dcc); + dcc_close (dcc, STAT_DONE, FALSE); + dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ + sprintf (buf, "%d", dcc->cps); + EMIT_SIGNAL (XP_TE_DCCRECVCOMP, dcc->serv->front_session, + dcc->file, dcc->destfile, dcc->nick, buf, 0); + return TRUE; + } + } +} + +static void +dcc_open_query (server *serv, char *nick) +{ + if (prefs.autodialog) + open_query (serv, nick, FALSE); +} + +static gboolean +dcc_did_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int er; + struct sockaddr_in addr; + +#ifdef WIN32 + if (condition & G_IO_ERR) + { + int len; + + /* find the last errno for this socket */ + len = sizeof (er); + getsockopt (dcc->sok, SOL_SOCKET, SO_ERROR, (char *)&er, &len); + EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session, + dcctypes[dcc->type], dcc->nick, errorstring (er), + NULL, 0); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return FALSE; + } + +#else + memset (&addr, 0, sizeof (addr)); + addr.sin_port = htons (dcc->port); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl (dcc->addr); + + /* check if it's already connected; This always fails on winXP */ + if (connect (dcc->sok, (struct sockaddr *) &addr, sizeof (addr)) != 0) + { + er = sock_error (); + if (er != EISCONN) + { + EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session, + dcctypes[dcc->type], dcc->nick, errorstring (er), + NULL, 0); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return FALSE; + } + } +#endif + + return TRUE; +} + +static gboolean +dcc_connect_finished (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char host[128]; + + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + + if (!dcc_did_connect (source, condition, dcc)) + return TRUE; + + dcc->dccstat = STAT_ACTIVE; + snprintf (host, sizeof host, "%s:%d", net_ip (dcc->addr), dcc->port); + + switch (dcc->type) + { + case TYPE_RECV: + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc); + EMIT_SIGNAL (XP_TE_DCCCONRECV, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + case TYPE_SEND: + /* passive send */ + dcc->fastsend = prefs.fastdccsend; + if (dcc->fastsend) + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE, dcc_send_data, dcc); + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); + dcc_send_data (NULL, 0, (gpointer)dcc); + EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + case TYPE_CHATSEND: /* pchat */ + dcc_open_query (dcc->serv, dcc->nick); + case TYPE_CHATRECV: /* normal chat */ + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + dcc->dccchat = malloc (sizeof (struct dcc_chat)); + dcc->dccchat->pos = 0; + EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, + dcc->nick, host, NULL, NULL, 0); + break; + } + dcc->starttime = time (0); + dcc->lasttime = dcc->starttime; + fe_dcc_update (dcc); + + return TRUE; +} + +static gboolean +read_proxy (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (proxy->bufferused < proxy->buffersize) + { + int ret = recv (dcc->sok, &proxy->buffer[proxy->bufferused], + proxy->buffersize - proxy->bufferused, 0); + if (ret > 0) + proxy->bufferused += ret; + else + { + if (would_block ()) + return FALSE; + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +write_proxy (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (proxy->bufferused < proxy->buffersize) + { + int ret = send (dcc->sok, &proxy->buffer[proxy->bufferused], + proxy->buffersize - proxy->bufferused, 0); + if (ret >= 0) + proxy->bufferused += ret; + else + { + if (would_block ()) + return FALSE; + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +proxy_read_line (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (1) + { + proxy->buffersize = proxy->bufferused + 1; + if (!read_proxy (dcc)) + return FALSE; + if (proxy->buffer[proxy->bufferused - 1] == '\n' + || proxy->bufferused == MAX_PROXY_BUFFER) + { + proxy->buffer[proxy->bufferused - 1] = 0; + return TRUE; + } + } +} + +static gboolean +dcc_wingate_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + if (proxy->phase == 0) + { + proxy->buffersize = snprintf ((char*) proxy->buffer, MAX_PROXY_BUFFER, + "%s %d\r\n", net_ip(dcc->addr), + dcc->port); + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_wingate_proxy_traverse, dcc); + ++proxy->phase; + } + if (proxy->phase == 1) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + dcc_connect_finished (source, 0, dcc); + } + return TRUE; +} + +struct sock_connect +{ + char version; + char type; + guint16 port; + guint32 address; + char username[10]; +}; +static gboolean +dcc_socks_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + + if (proxy->phase == 0) + { + struct sock_connect sc; + sc.version = 4; + sc.type = 1; + sc.port = htons (dcc->port); + sc.address = htonl (dcc->addr); + strncpy (sc.username, prefs.username, 9); + memcpy (proxy->buffer, &sc, sizeof (sc)); + proxy->buffersize = 8 + strlen (sc.username) + 1; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + proxy->buffersize = 8; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + if (proxy->buffer[1] == 90) + dcc_connect_finished (source, 0, dcc); + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + } + } + + return TRUE; +} + +struct sock5_connect1 +{ + char version; + char nmethods; + char method; +}; +static gboolean +dcc_socks5_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0]; + + if (proxy->phase == 0) + { + struct sock5_connect1 sc1; + + sc1.version = 5; + sc1.nmethods = 1; + sc1.method = 0; + if (auth) + sc1.method = 2; + memcpy (proxy->buffer, &sc1, 3); + proxy->buffersize = 3; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + proxy->buffersize = 2; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + + /* did the server say no auth required? */ + if (proxy->buffer[0] == 5 && proxy->buffer[1] == 0) + auth = 0; + + /* Set up authentication I/O */ + if (auth) + { + int len_u=0, len_p=0; + + /* authentication sub-negotiation (RFC1929) */ + if ( proxy->buffer[0] != 5 || proxy->buffer[1] != 2 ) /* UPA not supported by server */ + { + PrintText (dcc->serv->front_session, "SOCKS\tServer doesn't support UPA authentication.\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + + memset (proxy->buffer, 0, MAX_PROXY_BUFFER); + + /* form the UPA request */ + len_u = strlen (prefs.proxy_user); + len_p = strlen (prefs.proxy_pass); + proxy->buffer[0] = 1; + proxy->buffer[1] = len_u; + memcpy (proxy->buffer + 2, prefs.proxy_user, len_u); + proxy->buffer[2 + len_u] = len_p; + memcpy (proxy->buffer + 3 + len_u, prefs.proxy_pass, len_p); + + proxy->buffersize = 3 + len_u + len_p; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + else + { + if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0) + { + PrintText (dcc->serv->front_session, "SOCKS\tAuthentication required but disabled in settings.\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + proxy->phase += 2; + } + } + + if (proxy->phase == 3) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->buffersize = 2; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 4) + { + if (!read_proxy (dcc)) + return TRUE; + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + if (proxy->buffer[1] != 0) + { + PrintText (dcc->serv->front_session, "SOCKS\tAuthentication failed. " + "Is username and password correct?\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + ++proxy->phase; + } + + if (proxy->phase == 5) + { + proxy->buffer[0] = 5; /* version (socks 5) */ + proxy->buffer[1] = 1; /* command (connect) */ + proxy->buffer[2] = 0; /* reserved */ + proxy->buffer[3] = 1; /* address type (IPv4) */ + proxy->buffer[4] = (dcc->addr >> 24) & 0xFF; /* IP address */ + proxy->buffer[5] = (dcc->addr >> 16) & 0xFF; + proxy->buffer[6] = (dcc->addr >> 8) & 0xFF; + proxy->buffer[7] = (dcc->addr & 0xFF); + proxy->buffer[8] = (dcc->port >> 8) & 0xFF; /* port */ + proxy->buffer[9] = (dcc->port & 0xFF); + proxy->buffersize = 10; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 6) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->buffersize = 4; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 7) + { + if (!read_proxy (dcc)) + return TRUE; + if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + if (proxy->buffer[1] == 2) + PrintText (dcc->serv->front_session, "SOCKS\tProxy refused to connect to host (not allowed).\n"); + else + PrintTextf (dcc->serv->front_session, "SOCKS\tProxy failed to connect to host (error %d).\n", proxy->buffer[1]); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + switch (proxy->buffer[3]) + { + case 1: proxy->buffersize += 6; break; + case 3: proxy->buffersize += 1; break; + case 4: proxy->buffersize += 18; break; + }; + ++proxy->phase; + } + + if (proxy->phase == 8) + { + if (!read_proxy (dcc)) + return TRUE; + /* handle domain name case */ + if (proxy->buffer[3] == 3) + { + proxy->buffersize = 5 + proxy->buffer[4] + 2; + } + /* everything done? */ + if (proxy->bufferused == proxy->buffersize) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + dcc_connect_finished (source, 0, dcc); + } + } + return TRUE; +} + +static gboolean +dcc_http_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + + if (proxy->phase == 0) + { + char buf[256]; + char auth_data[128]; + char auth_data2[68]; + int n, n2; + + n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n", + net_ip(dcc->addr), dcc->port); + if (prefs.proxy_auth) + { + n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s", + prefs.proxy_user, prefs.proxy_pass); + base64_encode (auth_data, auth_data2, n2); + n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data); + } + n += snprintf (buf+n, sizeof (buf)-n, "\r\n"); + proxy->buffersize = n; + proxy->bufferused = 0; + memcpy (proxy->buffer, buf, proxy->buffersize); + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_http_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_http_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!proxy_read_line (dcc)) + return TRUE; + /* "HTTP/1.0 200 OK" */ + if (proxy->bufferused < 12 || + memcmp (proxy->buffer, "HTTP/", 5) || memcmp (proxy->buffer + 9, "200", 3)) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + PrintText (dcc->serv->front_session, proxy->buffer); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + proxy->bufferused = 0; + ++proxy->phase; + } + + if (proxy->phase == 3) + { + while (1) + { + /* read until blank line */ + if (proxy_read_line (dcc)) + { + if (proxy->bufferused < 1 || + (proxy->bufferused == 2 && proxy->buffer[0] == '\r')) + { + break; + } + if (proxy->bufferused > 1) + PrintText (dcc->serv->front_session, proxy->buffer); + proxy->bufferused = 0; + } + else + return TRUE; + } + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + dcc_connect_finished (source, 0, dcc); + } + + return TRUE; +} + +static gboolean +dcc_proxy_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + + if (!dcc_did_connect (source, condition, dcc)) + return TRUE; + + dcc->proxy = malloc (sizeof (struct proxy_state)); + if (!dcc->proxy) + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + memset (dcc->proxy, 0, sizeof (struct proxy_state)); + + switch (prefs.proxy_type) + { + case 1: return dcc_wingate_proxy_traverse (source, condition, dcc); + case 2: return dcc_socks_proxy_traverse (source, condition, dcc); + case 3: return dcc_socks5_proxy_traverse (source, condition, dcc); + case 4: return dcc_http_proxy_traverse (source, condition, dcc); + } + return TRUE; +} + +static int dcc_listen_init (struct DCC *, struct session *); + +static void +dcc_connect (struct DCC *dcc) +{ + int ret; + char tbuf[400]; + + if (dcc->dccstat == STAT_CONNECTING) + return; + dcc->dccstat = STAT_CONNECTING; + + if (dcc->pasvid && dcc->port == 0) + { + /* accepted a passive dcc send */ + ret = dcc_listen_init (dcc, dcc->serv->front_session); + if (!ret) + { + dcc_close (dcc, STAT_FAILED, FALSE); + return; + } + /* possible problems with filenames containing spaces? */ + if (dcc->type == TYPE_RECV) + snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ? + "DCC SEND \"%s\" %u %d %"DCC_SFMT" %d" : + "DCC SEND %s %u %d %"DCC_SFMT" %d", dcc->file, + dcc->addr, dcc->port, dcc->size, dcc->pasvid); + else + snprintf (tbuf, sizeof (tbuf), "DCC CHAT chat %u %d %d", + dcc->addr, dcc->port, dcc->pasvid); + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + } + else + { + dcc->sok = dcc_connect_sok (dcc); + if (dcc->sok == -1) + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return; + } + if (DCC_USE_PROXY ()) + dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_proxy_connect, dcc); + else + dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_connect_finished, dcc); + } + + fe_dcc_update (dcc); +} + +static gboolean +dcc_send_data (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char *buf; + int len, sent, sok = dcc->sok; + + if (prefs.dcc_blocksize < 1) /* this is too little! */ + prefs.dcc_blocksize = 1024; + + if (prefs.dcc_blocksize > 102400) /* this is too much! */ + prefs.dcc_blocksize = 102400; + + if (dcc->throttled) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + return FALSE; + } + + if (!dcc->fastsend) + { + if (dcc->ack < dcc->pos) + return TRUE; + } + else if (!dcc->wiotag) + dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); + + buf = malloc (prefs.dcc_blocksize); + if (!buf) + return TRUE; + + lseek (dcc->fp, dcc->pos, SEEK_SET); + len = read (dcc->fp, buf, prefs.dcc_blocksize); + if (len < 1) + goto abortit; + sent = send (sok, buf, len, 0); + + if (sent < 0 && !(would_block ())) + { +abortit: + free (buf); + EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, + errorstring (sock_error ()), NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + if (sent > 0) + { + dcc->pos += sent; + dcc->lasttime = time (0); + } + + /* have we sent it all yet? */ + if (dcc->pos >= dcc->size) + { + /* it's all sent now, so remove the WRITE/SEND handler */ + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + } + + free (buf); + + return TRUE; +} + +static gboolean +dcc_handle_new_ack (struct DCC *dcc) +{ + guint32 ack; + char buf[16]; + gboolean done = FALSE; + + memcpy (&ack, dcc->ack_buf, 4); + dcc->ack = ntohl (ack); + + /* this could mess up when xfering >32bit files */ + if (dcc->size <= 0xffffffff) + { + /* fix for BitchX */ + if (dcc->ack < dcc->resumable) + dcc->ackoffset = TRUE; + if (dcc->ackoffset) + dcc->ack += dcc->resumable; + } + + /* DCC complete check */ + if (dcc->pos >= dcc->size && dcc->ack >= (dcc->size & 0xffffffff)) + { + dcc->ack = dcc->size; /* force 100% ack for >4 GB */ + dcc_close (dcc, STAT_DONE, FALSE); + dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ + sprintf (buf, "%d", dcc->cps); + EMIT_SIGNAL (XP_TE_DCCSENDCOMP, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, buf, NULL, 0); + done = TRUE; + } + else if ((!dcc->fastsend) && (dcc->ack >= (dcc->pos & 0xffffffff))) + { + dcc_send_data (NULL, 0, (gpointer)dcc); + } + +#ifdef USE_DCC64 + /* take the top 32 of "bytes send" and bottom 32 of "ack" */ + dcc->ack = (dcc->pos & G_GINT64_CONSTANT (0xffffffff00000000)) | + (dcc->ack & 0xffffffff); + /* dcc->ack is only used for CPS and PERCENTAGE calcs from now on... */ +#endif + + return done; +} + +static gboolean +dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int len; + + while (1) + { + /* try to fill up 4 bytes */ + len = recv (dcc->sok, dcc->ack_buf, 4 - dcc->ack_pos, 0); + if (len < 1) + { + if (len < 0) + { + if (would_block ()) /* ok - keep waiting */ + return TRUE; + } + EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, + errorstring ((len < 0) ? sock_error () : 0), NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + dcc->ack_pos += len; + if (dcc->ack_pos >= 4) + { + dcc->ack_pos = 0; + if (dcc_handle_new_ack (dcc)) + return TRUE; + } + /* loop again until would_block() returns true */ + } +} + +static gboolean +dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char host[128]; + struct sockaddr_in CAddr; + int sok; + socklen_t len; + + len = sizeof (CAddr); + sok = accept (dcc->sok, (struct sockaddr *) &CAddr, &len); + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + closesocket (dcc->sok); + if (sok < 0) + { + dcc->sok = -1; + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + set_nonblocking (sok); + dcc->sok = sok; + dcc->addr = ntohl (CAddr.sin_addr.s_addr); + + if (dcc->pasvid) + return dcc_connect_finished (NULL, 0, dcc); + + dcc->dccstat = STAT_ACTIVE; + dcc->lasttime = dcc->starttime = time (0); + dcc->fastsend = prefs.fastdccsend; + + snprintf (host, sizeof (host), "%s:%d", net_ip (dcc->addr), dcc->port); + + switch (dcc->type) + { + case TYPE_SEND: + if (dcc->fastsend) + dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); + dcc->iotag = fe_input_add (sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); + dcc_send_data (NULL, 0, (gpointer)dcc); + EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + + case TYPE_CHATSEND: + dcc_open_query (dcc->serv, dcc->nick); + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + dcc->dccchat = malloc (sizeof (struct dcc_chat)); + dcc->dccchat->pos = 0; + EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, + dcc->nick, host, NULL, NULL, 0); + break; + } + + fe_dcc_update (dcc); + + return TRUE; +} + +guint32 +dcc_get_my_address (void) /* the address we'll tell the other person */ +{ + struct hostent *dns_query; + guint32 addr = 0; + + if (prefs.ip_from_server && prefs.dcc_ip) + addr = prefs.dcc_ip; + else if (prefs.dcc_ip_str[0]) + { + dns_query = gethostbyname ((const char *) prefs.dcc_ip_str); + + if (dns_query != NULL && + dns_query->h_length == 4 && + dns_query->h_addr_list[0] != NULL) + { + /*we're offered at least one IPv4 address: we take the first*/ + addr = *((guint32*) dns_query->h_addr_list[0]); + } + } + + return addr; +} + +static int +dcc_listen_init (struct DCC *dcc, session *sess) +{ + guint32 my_addr; + struct sockaddr_in SAddr; + int i, bindretval = -1; + socklen_t len; + + dcc->sok = socket (AF_INET, SOCK_STREAM, 0); + if (dcc->sok == -1) + return FALSE; + + memset (&SAddr, 0, sizeof (struct sockaddr_in)); + + len = sizeof (SAddr); + getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len); + + SAddr.sin_family = AF_INET; + + /*if local_ip is specified use that*/ + if (prefs.local_ip != 0xffffffff) + { + my_addr = prefs.local_ip; + SAddr.sin_addr.s_addr = prefs.local_ip; + } + /*otherwise use the default*/ + else + my_addr = SAddr.sin_addr.s_addr; + + /*if we have a valid portrange try to use that*/ + if (prefs.first_dcc_send_port > 0) + { + SAddr.sin_port = 0; + i = 0; + while ((prefs.last_dcc_send_port > ntohs(SAddr.sin_port)) && + (bindretval == -1)) + { + SAddr.sin_port = htons (prefs.first_dcc_send_port + i); + i++; + /*printf("Trying to bind against port: %d\n",ntohs(SAddr.sin_port));*/ + bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); + } + + /* with a small port range, reUseAddr is needed */ + len = 1; + setsockopt (dcc->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len)); + + } else + { + /* try random port */ + SAddr.sin_port = 0; + bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); + } + + if (bindretval == -1) + { + /* failed to bind */ + PrintText (sess, "Failed to bind to any address or port.\n"); + return FALSE; + } + + len = sizeof (SAddr); + getsockname (dcc->sok, (struct sockaddr *) &SAddr, &len); + + dcc->port = ntohs (SAddr.sin_port); + + /*if we have a dcc_ip, we use that, so the remote client can connect*/ + /*else we try to take an address from dcc_ip_str*/ + /*if something goes wrong we tell the client to connect to our LAN ip*/ + dcc->addr = dcc_get_my_address (); + + /*if nothing else worked we use the address we bound to*/ + if (dcc->addr == 0) + dcc->addr = my_addr; + + dcc->addr = ntohl (dcc->addr); + + set_nonblocking (dcc->sok); + listen (dcc->sok, 1); + set_blocking (dcc->sok); + + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_accept, dcc); + + return TRUE; +} + +static struct session *dccsess; +static char *dccto; /* lame!! */ +static int dccmaxcps; +static int recursive = FALSE; + +static void +dcc_send_wild (char *file) +{ + dcc_send (dccsess, dccto, file, dccmaxcps, 0); +} + +void +dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive) +{ + char outbuf[512]; + struct stat st; + struct DCC *dcc; + char *file_fs; + + /* this is utf8 */ + file = expand_homedir (file); + + if (!recursive && (strchr (file, '*') || strchr (file, '?'))) + { + char path[256]; + char wild[256]; + char *path_fs; /* local filesystem encoding */ + + safe_strcpy (wild, file_part (file), sizeof (wild)); + path_part (file, path, sizeof (path)); + if (path[0] != '/' || path[1] != '\0') + path[strlen (path) - 1] = 0; /* remove trailing slash */ + + dccsess = sess; + dccto = to; + dccmaxcps = maxcps; + + free (file); + + /* for_files() will use opendir, so we need local FS encoding */ + path_fs = xchat_filename_from_utf8 (path, -1, 0, 0, 0); + if (path_fs) + { + recursive = TRUE; + for_files (path_fs, wild, dcc_send_wild); + recursive = FALSE; + g_free (path_fs); + } + + return; + } + + dcc = new_dcc (); + if (!dcc) + return; + dcc->file = file; + dcc->maxcps = maxcps; + + /* get the local filesystem encoding */ + file_fs = xchat_filename_from_utf8 (file, -1, 0, 0, 0); + + if (stat (file_fs, &st) != -1) + { + +#ifndef USE_DCC64 + if (sizeof (st.st_size) > 4 && st.st_size > 4294967295U) + { + PrintText (sess, "Cannot send files larger than 4 GB.\n"); + goto xit; + } +#endif + + if (!(*file_part (file_fs)) || S_ISDIR (st.st_mode) || st.st_size < 1) + { + PrintText (sess, "Cannot send directories or empty files.\n"); + goto xit; + } + + dcc->starttime = dcc->offertime = time (0); + dcc->serv = sess->server; + dcc->dccstat = STAT_QUEUED; + dcc->size = st.st_size; + dcc->type = TYPE_SEND; + dcc->fp = open (file_fs, OFLAGS | O_RDONLY); + if (dcc->fp != -1) + { + g_free (file_fs); + if (passive || dcc_listen_init (dcc, sess)) + { + char havespaces = 0; + file = dcc->file; + while (*file) + { + if (*file == ' ') + { + if (prefs.dcc_send_fillspaces) + *file = '_'; + else + havespaces = 1; + } + file++; + } + dcc->nick = strdup (to); + if (prefs.autoopendccsendwindow) + { + if (fe_dcc_open_send_win (TRUE)) /* already open? add */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (passive) + { + dcc->pasvid = new_id(); + snprintf (outbuf, sizeof (outbuf), (havespaces) ? + "DCC SEND \"%s\" 199 0 %"DCC_SFMT" %d" : + "DCC SEND %s 199 0 %"DCC_SFMT" %d", + file_part (dcc->file), + dcc->size, dcc->pasvid); + } else + { + snprintf (outbuf, sizeof (outbuf), (havespaces) ? + "DCC SEND \"%s\" %u %d %"DCC_SFMT : + "DCC SEND %s %u %d %"DCC_SFMT, + file_part (dcc->file), dcc->addr, + dcc->port, dcc->size); + } + sess->server->p_ctcp (sess->server, to, outbuf); + + EMIT_SIGNAL (XP_TE_DCCOFFER, sess, file_part (dcc->file), + to, dcc->file, NULL, 0); + } else + { + dcc_close (dcc, 0, TRUE); + } + return; + } + } + PrintTextf (sess, _("Cannot access %s\n"), dcc->file); + PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); +xit: + g_free (file_fs); + dcc_close (dcc, 0, TRUE); +} + +static struct DCC * +find_dcc_from_id (int id, int type) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->pasvid == id && + dcc->dccstat == STAT_QUEUED && dcc->type == type) + return dcc; + list = list->next; + } + return 0; +} + +static struct DCC * +find_dcc_from_port (int port, int type) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->port == port && + dcc->dccstat == STAT_QUEUED && dcc->type == type) + return dcc; + list = list->next; + } + return 0; +} + +struct DCC * +find_dcc (char *nick, char *file, int type) +{ + GSList *list = dcc_list; + struct DCC *dcc; + while (list) + { + dcc = (struct DCC *) list->data; + if (nick == NULL || !rfc_casecmp (nick, dcc->nick)) + { + if (type == -1 || dcc->type == type) + { + if (!file[0]) + return dcc; + if (!strcasecmp (file, file_part (dcc->file))) + return dcc; + if (!strcasecmp (file, dcc->file)) + return dcc; + } + } + list = list->next; + } + return 0; +} + +/* called when we receive a NICK change from server */ + +void +dcc_change_nick (struct server *serv, char *oldnick, char *newnick) +{ + struct DCC *dcc; + GSList *list = dcc_list; + + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->serv == serv) + { + if (!serv->p_cmp (dcc->nick, oldnick)) + { + if (dcc->nick) + free (dcc->nick); + dcc->nick = strdup (newnick); + } + } + list = list->next; + } +} + +/* is the destination file the same? new_dcc is not opened yet */ + +static int +is_same_file (struct DCC *dcc, struct DCC *new_dcc) +{ + struct stat st_a, st_b; + + /* if it's the same filename, must be same */ + if (strcmp (dcc->destfile, new_dcc->destfile) == 0) + return TRUE; + + /* now handle case-insensitive Filesystems: HFS+, FAT */ +#ifdef WIN32 +#warning no win32 implementation - behaviour may be unreliable +#else + /* this fstat() shouldn't really fail */ + if ((dcc->fp == -1 ? stat (dcc->destfile_fs, &st_a) : fstat (dcc->fp, &st_a)) == -1) + return FALSE; + if (stat (new_dcc->destfile_fs, &st_b) == -1) + return FALSE; + + /* same inode, same device, same file! */ + if (st_a.st_ino == st_b.st_ino && + st_a.st_dev == st_b.st_dev) + return TRUE; +#endif + + return FALSE; +} + +static int +is_resumable (struct DCC *dcc) +{ + dcc->resumable = 0; + + /* Check the file size */ + if (access (dcc->destfile_fs, W_OK) == 0) + { + struct stat st; + + if (stat (dcc->destfile_fs, &st) != -1) + { + if (st.st_size < dcc->size) + { + dcc->resumable = st.st_size; + dcc->pos = st.st_size; + } + else + dcc->resume_error = 2; + } else + { + dcc->resume_errno = errno; + dcc->resume_error = 1; + } + } else + { + dcc->resume_errno = errno; + dcc->resume_error = 1; + } + + /* Now verify that this DCC is not already in progress from someone else */ + + if (dcc->resumable) + { + GSList *list = dcc_list; + struct DCC *d; + while (list) + { + d = list->data; + if (d->type == TYPE_RECV && d->dccstat != STAT_ABORTED && + d->dccstat != STAT_DONE && d->dccstat != STAT_FAILED) + { + if (d != dcc && is_same_file (d, dcc)) + { + dcc->resume_error = 3; /* dccgui.c uses it */ + dcc->resumable = 0; + dcc->pos = 0; + break; + } + } + list = list->next; + } + } + + return dcc->resumable; +} + +void +dcc_get (struct DCC *dcc) +{ + switch (dcc->dccstat) + { + case STAT_QUEUED: + if (dcc->type != TYPE_CHATSEND) + { + if (dcc->type == TYPE_RECV && prefs.autoresume && dcc->resumable) + { + dcc_resume (dcc); + } + else + { + dcc->resumable = 0; + dcc->pos = 0; + dcc_connect (dcc); + } + } + break; + case STAT_DONE: + case STAT_FAILED: + case STAT_ABORTED: + dcc_close (dcc, 0, TRUE); + break; + } +} + +/* for "Save As..." dialog */ + +void +dcc_get_with_destfile (struct DCC *dcc, char *file) +{ + g_free (dcc->destfile); + dcc->destfile = g_strdup (file); /* utf-8 */ + + /* get the local filesystem encoding */ + g_free (dcc->destfile_fs); + dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); + + /* since destfile changed, must check resumability again */ + is_resumable (dcc); + + dcc_get (dcc); +} + +void +dcc_get_nick (struct session *sess, char *nick) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (!sess->server->p_cmp (nick, dcc->nick)) + { + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV) + { + dcc->resumable = 0; + dcc->pos = 0; + dcc->ack = 0; + dcc_connect (dcc); + return; + } + } + list = list->next; + } + if (sess) + EMIT_SIGNAL (XP_TE_DCCIVAL, sess, NULL, NULL, NULL, NULL, 0); +} + +static struct DCC * +new_dcc (void) +{ + struct DCC *dcc = malloc (sizeof (struct DCC)); + if (!dcc) + return 0; + memset (dcc, 0, sizeof (struct DCC)); + dcc->sok = -1; + dcc->fp = -1; + dcc_list = g_slist_prepend (dcc_list, dcc); + return (dcc); +} + +void +dcc_chat (struct session *sess, char *nick, int passive) +{ + char outbuf[512]; + struct DCC *dcc; + + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_ACTIVE: + case STAT_QUEUED: + case STAT_CONNECTING: + EMIT_SIGNAL (XP_TE_DCCCHATREOFFER, sess, nick, NULL, NULL, NULL, 0); + return; + case STAT_ABORTED: + case STAT_FAILED: + dcc_close (dcc, 0, TRUE); + } + } + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_QUEUED: + dcc_connect (dcc); + break; + case STAT_FAILED: + case STAT_ABORTED: + dcc_close (dcc, 0, TRUE); + } + return; + } + /* offer DCC CHAT */ + + dcc = new_dcc (); + if (!dcc) + return; + dcc->starttime = dcc->offertime = time (0); + dcc->serv = sess->server; + dcc->dccstat = STAT_QUEUED; + dcc->type = TYPE_CHATSEND; + dcc->nick = strdup (nick); + if (passive || dcc_listen_init (dcc, sess)) + { + if (prefs.autoopendccchatwindow) + { + if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (passive) + { + dcc->pasvid = new_id (); + snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d", + dcc->port, dcc->pasvid); + } else + { + snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat %u %d", + dcc->addr, dcc->port); + } + dcc->serv->p_ctcp (dcc->serv, nick, outbuf); + EMIT_SIGNAL (XP_TE_DCCCHATOFFERING, sess, nick, NULL, NULL, NULL, 0); + } else + { + dcc_close (dcc, 0, TRUE); + } +} + +static void +dcc_malformed (struct session *sess, char *nick, char *data) +{ + EMIT_SIGNAL (XP_TE_MALFORMED, sess, nick, data, NULL, NULL, 0); +} + +int +dcc_resume (struct DCC *dcc) +{ + char tbuf[500]; + + if (dcc->dccstat == STAT_QUEUED && dcc->resumable) + { + dcc->resume_sent = 1; + /* filename contains spaces? Quote them! */ + snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ? + "DCC RESUME \"%s\" %d %"DCC_SFMT : + "DCC RESUME %s %d %"DCC_SFMT, + dcc->file, dcc->port, dcc->resumable); + + if (dcc->pasvid) + sprintf (tbuf + strlen (tbuf), " %d", dcc->pasvid); + + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + return 1; + } + + return 0; +} + +static void +dcc_confirm_send (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_get (dcc); +} + +static void +dcc_deny_send (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_abort (dcc->serv->front_session, dcc); +} + +static void +dcc_confirm_chat (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_connect (dcc); +} + +static void +dcc_deny_chat (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_abort (dcc->serv->front_session, dcc); +} + +static struct DCC * +dcc_add_chat (session *sess, char *nick, int port, guint32 addr, int pasvid) +{ + struct DCC *dcc; + + dcc = new_dcc (); + if (dcc) + { + dcc->serv = sess->server; + dcc->type = TYPE_CHATRECV; + dcc->dccstat = STAT_QUEUED; + dcc->addr = addr; + dcc->port = port; + dcc->pasvid = pasvid; + dcc->nick = strdup (nick); + dcc->starttime = time (0); + + EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick, + NULL, NULL, NULL, 0); + + if (prefs.autoopendccchatwindow) + { + if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (prefs.autodccchat == 1) + dcc_connect (dcc); + else if (prefs.autodccchat == 2) + { + char buff[128]; + snprintf (buff, sizeof (buff), "%s is offering DCC Chat. Do you want to accept?", nick); + fe_confirm (buff, dcc_confirm_chat, dcc_deny_chat, dcc); + } + } + + return dcc; +} + +static struct DCC * +dcc_add_file (session *sess, char *file, DCC_SIZE size, int port, char *nick, guint32 addr, int pasvid) +{ + struct DCC *dcc; + char tbuf[512]; + + dcc = new_dcc (); + if (dcc) + { + dcc->file = strdup (file); + + dcc->destfile = g_malloc (strlen (prefs.dccdir) + strlen (nick) + + strlen (file) + 4); + + strcpy (dcc->destfile, prefs.dccdir); + if (prefs.dccdir[strlen (prefs.dccdir) - 1] != '/') + strcat (dcc->destfile, "/"); + if (prefs.dccwithnick) + { +#ifdef WIN32 + char *t = strlen (dcc->destfile) + dcc->destfile; + strcpy (t, nick); + while (*t) + { + if (*t == '\\' || *t == '|') + *t = '_'; + t++; + } +#else + strcat (dcc->destfile, nick); +#endif + strcat (dcc->destfile, "."); + } + strcat (dcc->destfile, file); + + /* get the local filesystem encoding */ + dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); + + dcc->resumable = 0; + dcc->pos = 0; + dcc->serv = sess->server; + dcc->type = TYPE_RECV; + dcc->dccstat = STAT_QUEUED; + dcc->addr = addr; + dcc->port = port; + dcc->pasvid = pasvid; + dcc->size = size; + dcc->nick = strdup (nick); + dcc->maxcps = prefs.dcc_max_get_cps; + + is_resumable (dcc); + + /* autodccsend is really autodccrecv.. right? */ + + if (prefs.autodccsend == 1) + { + dcc_get (dcc); + } + else if (prefs.autodccsend == 2) + { + snprintf (tbuf, sizeof (tbuf), _("%s is offering \"%s\". Do you want to accept?"), nick, file); + fe_confirm (tbuf, dcc_confirm_send, dcc_deny_send, dcc); + } + if (prefs.autoopendccrecvwindow) + { + if (fe_dcc_open_recv_win (TRUE)) /* was already open? just add*/ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + } + sprintf (tbuf, "%"DCC_SFMT, size); + snprintf (tbuf + 24, 300, "%s:%d", net_ip (addr), port); + EMIT_SIGNAL (XP_TE_DCCSENDOFFER, sess->server->front_session, nick, + file, tbuf, tbuf + 24, 0); + + return dcc; +} + +void +handle_dcc (struct session *sess, char *nick, char *word[], + char *word_eol[]) +{ + char tbuf[512]; + struct DCC *dcc; + char *type = word[5]; + int port, pasvid = 0; + guint32 addr; + DCC_SIZE size; + int psend = 0; + + if (!strcasecmp (type, "CHAT")) + { + port = atoi (word[8]); + addr = strtoul (word[7], NULL, 10); + + if (port == 0) + pasvid = atoi (word[9]); + else if (word[9][0] != 0) + { + pasvid = atoi (word[9]); + psend = 1; + } + + if (!addr /*|| (port < 1024 && port != 0)*/ + || port > 0xffff || (port == 0 && pasvid == 0)) + { + dcc_malformed (sess, nick, word_eol[4] + 2); + return; + } + + if (psend) + { + dcc = find_dcc_from_id (pasvid, TYPE_CHATSEND); + if (dcc) + { + dcc->addr = addr; + dcc->port = port; + dcc_connect (dcc); + } else + { + dcc_malformed (sess, nick, word_eol[4] + 2); + } + return; + } + + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc) + dcc_close (dcc, 0, TRUE); + + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (dcc) + dcc_close (dcc, 0, TRUE); + + dcc_add_chat (sess, nick, port, addr, pasvid); + return; + } + + if (!strcasecmp (type, "Resume")) + { + port = atoi (word[7]); + + if (port == 0) + { /* PASSIVE */ + pasvid = atoi(word[9]); + dcc = find_dcc_from_id(pasvid, TYPE_SEND); + } else + { + dcc = find_dcc_from_port (port, TYPE_SEND); + } + if (!dcc) + dcc = find_dcc (nick, word[6], TYPE_SEND); + if (dcc) + { + size = BIG_STR_TO_INT (word[8]); + dcc->resumable = size; + if (dcc->resumable < dcc->size) + { + dcc->pos = dcc->resumable; + dcc->ack = dcc->resumable; + lseek (dcc->fp, dcc->pos, SEEK_SET); + + /* Checking if dcc is passive and if filename contains spaces */ + if (dcc->pasvid) + snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? + "DCC ACCEPT \"%s\" %d %"DCC_SFMT" %d" : + "DCC ACCEPT %s %d %"DCC_SFMT" %d", + file_part (dcc->file), port, dcc->resumable, dcc->pasvid); + else + snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? + "DCC ACCEPT \"%s\" %d %"DCC_SFMT : + "DCC ACCEPT %s %d %"DCC_SFMT, + file_part (dcc->file), port, dcc->resumable); + + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + } + sprintf (tbuf, "%"DCC_SFMT, dcc->pos); + EMIT_SIGNAL (XP_TE_DCCRESUMEREQUEST, sess, nick, + file_part (dcc->file), tbuf, NULL, 0); + } + return; + } + if (!strcasecmp (type, "Accept")) + { + port = atoi (word[7]); + dcc = find_dcc_from_port (port, TYPE_RECV); + if (dcc && dcc->dccstat == STAT_QUEUED) + { + dcc_connect (dcc); + } + return; + } + if (!strcasecmp (type, "SEND")) + { + char *file = file_part (word[6]); + + port = atoi (word[8]); + addr = strtoul (word[7], NULL, 10); + size = BIG_STR_TO_INT (word[9]); + + if (port == 0) /* Passive dcc requested */ + pasvid = atoi (word[10]); + else if (word[10][0] != 0) + { + /* Requesting passive dcc. + * Destination user of an active dcc is giving his + * TRUE address/port/pasvid data. + * This information will be used later to + * establish the connection to the user. + * We can recognize this type of dcc using word[10] + * because this field is always null (no pasvid) + * in normal dcc sends. + */ + pasvid = atoi (word[10]); + psend = 1; + } + + + if (!addr || !size /*|| (port < 1024 && port != 0)*/ + || port > 0xffff || (port == 0 && pasvid == 0)) + { + dcc_malformed (sess, nick, word_eol[4] + 2); + return; + } + + if (psend) + { + /* Third Step of Passive send. + * Connecting to the destination and finally + * sending file. + */ + dcc = find_dcc_from_id (pasvid, TYPE_SEND); + if (dcc) + { + dcc->addr = addr; + dcc->port = port; + dcc_connect (dcc); + } else + { + dcc_malformed (sess, nick, word_eol[4] + 2); + } + return; + } + + dcc_add_file (sess, file, size, port, nick, addr, pasvid); + + } else + { + EMIT_SIGNAL (XP_TE_DCCGENERICOFFER, sess->server->front_session, + word_eol[4] + 2, nick, NULL, NULL, 0); + } +} + +void +dcc_show_list (struct session *sess) +{ + int i = 0; + struct DCC *dcc; + GSList *list = dcc_list; + + EMIT_SIGNAL (XP_TE_DCCHEAD, sess, NULL, NULL, NULL, NULL, 0); + while (list) + { + dcc = (struct DCC *) list->data; + i++; + PrintTextf (sess, " %s %-10.10s %-7.7s %-7"DCC_SFMT" %-7"DCC_SFMT" %s\n", + dcctypes[dcc->type], dcc->nick, + _(dccstat[dcc->dccstat].name), dcc->size, dcc->pos, + file_part (dcc->file)); + list = list->next; + } + if (!i) + PrintText (sess, _("No active DCCs\n")); +} diff --git a/src/common/dcc.h b/src/common/dcc.h new file mode 100644 index 00000000..da4ce979 --- /dev/null +++ b/src/common/dcc.h @@ -0,0 +1,117 @@ +/* dcc.h */ + +#include <time.h> /* for time_t */ + +#ifndef XCHAT_DCC_H +#define XCHAT_DCC_H + +#define STAT_QUEUED 0 +#define STAT_ACTIVE 1 +#define STAT_FAILED 2 +#define STAT_DONE 3 +#define STAT_CONNECTING 4 +#define STAT_ABORTED 5 + +#define TYPE_SEND 0 +#define TYPE_RECV 1 +#define TYPE_CHATRECV 2 +#define TYPE_CHATSEND 3 + +#define CPS_AVG_WINDOW 10 + +/* can we do 64-bit dcc? */ +#if defined(G_GINT64_FORMAT) && defined(HAVE_STRTOULL) +#define USE_DCC64 +/* we really get only 63 bits, since st_size is signed */ +#define DCC_SIZE gint64 +#define DCC_SFMT G_GINT64_FORMAT +#else +#define DCC_SIZE unsigned int +#define DCC_SFMT "u" +#endif + +struct DCC +{ + struct server *serv; + struct dcc_chat *dccchat; + struct proxy_state *proxy; + guint32 addr; /* the 32bit IP number, host byte order */ + int fp; /* file pointer */ + int sok; + int iotag; /* reading io tag */ + int wiotag; /* writing/sending io tag */ + int port; + int pasvid; /* mIRC's passive DCC id */ + int cps; + int resume_error; + int resume_errno; + + GTimeVal lastcpstv, firstcpstv; + DCC_SIZE lastcpspos; + int maxcps; + + unsigned char ack_buf[4]; /* buffer for reading 4-byte ack */ + int ack_pos; + + DCC_SIZE size; + DCC_SIZE resumable; + DCC_SIZE ack; + DCC_SIZE pos; + time_t starttime; + time_t offertime; + time_t lasttime; + char *file; /* utf8 */ + char *destfile; /* utf8 */ + char *destfile_fs; /* local filesystem encoding */ + char *nick; + unsigned char type; /* 0 = SEND 1 = RECV 2 = CHAT */ + unsigned char dccstat; /* 0 = QUEUED 1 = ACTIVE 2 = FAILED 3 = DONE */ + unsigned int resume_sent:1; /* resume request sent */ + unsigned int fastsend:1; + unsigned int ackoffset:1; /* is reciever sending acks as an offset from */ + /* the resume point? */ + unsigned int throttled:2; /* 0x1 = per send/get throttle + 0x2 = global throttle */ +}; + +#define MAX_PROXY_BUFFER 1024 +struct proxy_state +{ + int phase; + unsigned char buffer[MAX_PROXY_BUFFER]; + int buffersize; + int bufferused; +}; + +struct dcc_chat +{ + char linebuf[2048]; + int pos; +}; + +struct dccstat_info +{ + char *name; /* Display name */ + int color; /* Display color (index into colors[] ) */ +}; + +extern struct dccstat_info dccstat[]; + +gboolean is_dcc (struct DCC *dcc); +void dcc_abort (session *sess, struct DCC *dcc); +void dcc_get (struct DCC *dcc); +int dcc_resume (struct DCC *dcc); +void dcc_check_timeouts (void); +void dcc_change_nick (server *serv, char *oldnick, char *newnick); +void dcc_notify_kill (struct server *serv); +struct DCC *dcc_write_chat (char *nick, char *text); +void dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive); +struct DCC *find_dcc (char *nick, char *file, int type); +void dcc_get_nick (struct session *sess, char *nick); +void dcc_chat (session *sess, char *nick, int passive); +void handle_dcc (session *sess, char *nick, char *word[], char *word_eol[]); +void dcc_show_list (session *sess); +guint32 dcc_get_my_address (void); +void dcc_get_with_destfile (struct DCC *dcc, char *utf8file); + +#endif diff --git a/src/common/fe.h b/src/common/fe.h new file mode 100644 index 00000000..16526581 --- /dev/null +++ b/src/common/fe.h @@ -0,0 +1,162 @@ +#include "userlist.h" +#include "dcc.h" + +#ifndef XCHAT_FE_H +#define XCHAT_FE_H + +/* for storage of /menu entries */ +typedef struct +{ + gint32 pos; /* position */ + gint16 modifier; /* keybinding */ + gint16 root_offset; /* bytes to offset ->path */ + + char is_main; /* is part of the Main menu? (not a popup) */ + char state; /* state of toggle items */ + char markup; /* use pango markup? */ + char enable; /* enabled? sensitivity */ + + int key; + char *path; + char *label; + char *cmd; + char *ucmd; /* unselect command (toggles) */ + char *group; /* for radio items or NULL */ + char *icon; /* filename */ +} menu_entry; + +int fe_args (int argc, char *argv[]); +void fe_init (void); +void fe_main (void); +void fe_cleanup (void); +void fe_exit (void); +int fe_timeout_add (int interval, void *callback, void *userdata); +void fe_timeout_remove (int tag); +void fe_new_window (struct session *sess, int focus); +void fe_new_server (struct server *serv); +void fe_add_rawlog (struct server *serv, char *text, int len, int outbound); +#define FE_MSG_WAIT 1 +#define FE_MSG_INFO 2 +#define FE_MSG_WARN 4 +#define FE_MSG_ERROR 8 +#define FE_MSG_MARKUP 16 +void fe_message (char *msg, int flags); +#define FIA_READ 1 +#define FIA_WRITE 2 +#define FIA_EX 4 +#define FIA_FD 8 +int fe_input_add (int sok, int flags, void *func, void *data); +void fe_input_remove (int tag); +void fe_idle_add (void *func, void *data); +void fe_set_topic (struct session *sess, char *topic, char *stripped_topic); +void fe_set_hilight (struct session *sess); +void fe_set_tab_color (struct session *sess, int col); +void fe_flash_window (struct session *sess); +void fe_update_mode_buttons (struct session *sess, char mode, char sign); +void fe_update_channel_key (struct session *sess); +void fe_update_channel_limit (struct session *sess); +int fe_is_chanwindow (struct server *serv); +void fe_add_chan_list (struct server *serv, char *chan, char *users, + char *topic); +void fe_chan_list_end (struct server *serv); +int fe_is_banwindow (struct session *sess); +void fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption); +void fe_ban_list_end (struct session *sess, int is_exemption); +void fe_notify_update (char *name); +void fe_notify_ask (char *name, char *networks); +void fe_text_clear (struct session *sess, int lines); +void fe_close_window (struct session *sess); +void fe_progressbar_start (struct session *sess); +void fe_progressbar_end (struct server *serv); +void fe_print_text (struct session *sess, char *text, time_t stamp); +void fe_userlist_insert (struct session *sess, struct User *newuser, int row, int sel); +int fe_userlist_remove (struct session *sess, struct User *user); +void fe_userlist_rehash (struct session *sess, struct User *user); +void fe_userlist_update (struct session *sess, struct User *user); +void fe_userlist_move (struct session *sess, struct User *user, int new_row); +void fe_userlist_numbers (struct session *sess); +void fe_userlist_clear (struct session *sess); +void fe_userlist_set_selected (struct session *sess); +void fe_uselect (session *sess, char *word[], int do_clear, int scroll_to); +void fe_dcc_add (struct DCC *dcc); +void fe_dcc_update (struct DCC *dcc); +void fe_dcc_remove (struct DCC *dcc); +int fe_dcc_open_recv_win (int passive); +int fe_dcc_open_send_win (int passive); +int fe_dcc_open_chat_win (int passive); +void fe_clear_channel (struct session *sess); +void fe_session_callback (struct session *sess); +void fe_server_callback (struct server *serv); +void fe_url_add (const char *text); +void fe_pluginlist_update (void); +void fe_buttons_update (struct session *sess); +void fe_dlgbuttons_update (struct session *sess); +void fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive); +void fe_set_channel (struct session *sess); +void fe_set_title (struct session *sess); +void fe_set_nonchannel (struct session *sess, int state); +void fe_set_nick (struct server *serv, char *newnick); +void fe_ignore_update (int level); +void fe_beep (void); +void fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp); +void fe_set_lag (server *serv, int lag); +void fe_set_throttle (server *serv); +void fe_set_away (server *serv); +void fe_serverlist_open (session *sess); +void fe_get_str (char *prompt, char *def, void *callback, void *ud); +void fe_get_int (char *prompt, int def, void *callback, void *ud); +#define FRF_WRITE 1 /* save file */ +#define FRF_MULTIPLE 2 /* multi-select */ +#define FRF_ADDFOLDER 4 /* add ~/.xchat2 to favourites */ +#define FRF_CHOOSEFOLDER 8 /* choosing a folder only */ +#define FRF_FILTERISINITIAL 16 /* unused */ +#define FRF_NOASKOVERWRITE 32 /* don't ask to overwrite existing files */ +void fe_get_file (const char *title, char *initial, + void (*callback) (void *userdata, char *file), void *userdata, + int flags); +typedef enum { + FE_GUI_HIDE, + FE_GUI_SHOW, + FE_GUI_FOCUS, + FE_GUI_FLASH, + FE_GUI_COLOR, + FE_GUI_ICONIFY, + FE_GUI_MENU, + FE_GUI_ATTACH, + FE_GUI_APPLY, +} fe_gui_action; +void fe_ctrl_gui (session *sess, fe_gui_action action, int arg); +int fe_gui_info (session *sess, int info_type); +void *fe_gui_info_ptr (session *sess, int info_type); +void fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud); +char *fe_get_inputbox_contents (struct session *sess); +int fe_get_inputbox_cursor (struct session *sess); +void fe_set_inputbox_contents (struct session *sess, char *text); +void fe_set_inputbox_cursor (struct session *sess, int delta, int pos); +void fe_open_url (const char *url); +void fe_menu_del (menu_entry *); +char *fe_menu_add (menu_entry *); +void fe_menu_update (menu_entry *); +#define FE_SE_CONNECT 0 +#define FE_SE_LOGGEDIN 1 +#define FE_SE_DISCONNECT 2 +#define FE_SE_RECONDELAY 3 +#define FE_SE_CONNECTING 4 +void fe_server_event (server *serv, int type, int arg); +/* pass NULL filename2 for default xchat icon */ +void fe_tray_set_flash (const char *filename1, const char *filename2, int timeout); +/* pass NULL filename for default xchat icon */ +void fe_tray_set_file (const char *filename); +typedef enum +{ + FE_ICON_NORMAL = 0, + FE_ICON_MESSAGE = 2, + FE_ICON_HIGHLIGHT = 5, + FE_ICON_PRIVMSG = 8, + FE_ICON_FILEOFFER = 11 +} feicon; +void fe_tray_set_icon (feicon icon); +void fe_tray_set_tooltip (const char *text); +void fe_tray_set_balloon (const char *title, const char *text); + +#endif diff --git a/src/common/history.c b/src/common/history.c new file mode 100644 index 00000000..1cd6b508 --- /dev/null +++ b/src/common/history.c @@ -0,0 +1,121 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <string.h> +#include <stdlib.h> +#include "history.h" + +void +history_add (struct history *his, char *text) +{ + if (his->lines[his->realpos]) + free (his->lines[his->realpos]); + his->lines[his->realpos] = strdup (text); + his->realpos++; + if (his->realpos == HISTORY_SIZE) + his->realpos = 0; + his->pos = his->realpos; +} + +void +history_free (struct history *his) +{ + int i; + for (i = 0; i < HISTORY_SIZE; i++) + { + if (his->lines[i]) + { + free (his->lines[i]); + his->lines[i] = 0; + } + } +} + +char * +history_down (struct history *his) +{ + int next; + + if (his->pos == his->realpos) /* allow down only after up */ + return 0; + if (his->realpos == 0) + { + if (his->pos == HISTORY_SIZE - 1) + { + his->pos = 0; + return ""; + } + } else + { + if (his->pos == his->realpos - 1) + { + his->pos++; + return ""; + } + } + + next = 0; + if (his->pos < HISTORY_SIZE - 1) + next = his->pos + 1; + + if (his->lines[next]) + { + his->pos = next; + return his->lines[his->pos]; + } + + return 0; +} + +char * +history_up (struct history *his, char *current_text) +{ + int next; + + if (his->realpos == HISTORY_SIZE - 1) + { + if (his->pos == 0) + return 0; + } else + { + if (his->pos == his->realpos + 1) + return 0; + } + + next = HISTORY_SIZE - 1; + if (his->pos != 0) + next = his->pos - 1; + + if (his->lines[next]) + { + if + ( + current_text[0] && strcmp(current_text, his->lines[next]) && + (!his->lines[his->pos] || strcmp(current_text, his->lines[his->pos])) && + (!his->lines[his->realpos] || strcmp(current_text, his->lines[his->pos])) + ) + { + history_add (his, current_text); + } + + his->pos = next; + return his->lines[his->pos]; + } + + return 0; +} diff --git a/src/common/history.h b/src/common/history.h new file mode 100644 index 00000000..5267f1fc --- /dev/null +++ b/src/common/history.h @@ -0,0 +1,18 @@ +#ifndef XCHAT_HISTORY_H +#define XCHAT_HISTORY_H + +#define HISTORY_SIZE 100 + +struct history +{ + char *lines[HISTORY_SIZE]; + int pos; + int realpos; +}; + +void history_add (struct history *his, char *text); +void history_free (struct history *his); +char *history_up (struct history *his, char *current_text); +char *history_down (struct history *his); + +#endif diff --git a/src/common/identd.c b/src/common/identd.c new file mode 100644 index 00000000..919282ea --- /dev/null +++ b/src/common/identd.c @@ -0,0 +1,89 @@ +/* simple identd server for xchat under win32 */ + + +static int identd_is_running = FALSE; + + +static int +identd (char *username) +{ + int sok, read_sok, len; + char *p; + char buf[256]; + char outbuf[256]; + struct sockaddr_in addr; + + sok = socket (AF_INET, SOCK_STREAM, 0); + if (sok == INVALID_SOCKET) + { + free (username); + return 0; + } + + len = 1; + setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len)); + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons (113); + + if (bind (sok, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR) + { + closesocket (sok); + free (username); + return 0; + } + + if (listen (sok, 1) == SOCKET_ERROR) + { + closesocket (sok); + free (username); + return 0; + } + + len = sizeof (addr); + read_sok = accept (sok, (struct sockaddr *) &addr, &len); + closesocket (sok); + if (read_sok == INVALID_SOCKET) + { + free (username); + return 0; + } + + identd_is_running = FALSE; + + snprintf (outbuf, sizeof (outbuf), "%%\tServicing ident request from %s\n", + inet_ntoa (addr.sin_addr)); + PrintText (current_sess, outbuf); + + recv (read_sok, buf, sizeof (buf) - 1, 0); + buf[sizeof (buf) - 1] = 0; /* ensure null termination */ + + p = strchr (buf, ','); + if (p) + { + snprintf (outbuf, sizeof (outbuf) - 1, "%d, %d : USERID : UNIX : %s\r\n", + atoi (buf), atoi (p + 1), username); + outbuf[sizeof (outbuf) - 1] = 0; /* ensure null termination */ + send (read_sok, outbuf, strlen (outbuf), 0); + } + + sleep (1); + closesocket (read_sok); + free (username); + + return 0; +} + +static void +identd_start (char *username) +{ + DWORD tid; + + if (identd_is_running == FALSE) + { + identd_is_running = TRUE; + CloseHandle (CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) identd, + strdup (username), 0, &tid)); + } +} diff --git a/src/common/ignore.c b/src/common/ignore.c new file mode 100644 index 00000000..c3544f30 --- /dev/null +++ b/src/common/ignore.c @@ -0,0 +1,424 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "xchat.h" +#include "ignore.h" +#include "cfgfiles.h" +#include "fe.h" +#include "text.h" +#include "util.h" +#include "xchatc.h" + + +int ignored_ctcp = 0; /* keep a count of all we ignore */ +int ignored_priv = 0; +int ignored_chan = 0; +int ignored_noti = 0; +int ignored_invi = 0; +static int ignored_total = 0; + +/* ignore_exists (): + * returns: struct ig, if this mask is in the ignore list already + * NULL, otherwise + */ +struct ignore * +ignore_exists (char *mask) +{ + struct ignore *ig = 0; + GSList *list; + + list = ignore_list; + while (list) + { + ig = (struct ignore *) list->data; + if (!rfc_casecmp (ig->mask, mask)) + return ig; + list = list->next; + } + return NULL; + +} + +/* ignore_add(...) + + * returns: + * 0 fail + * 1 success + * 2 success (old ignore has been changed) + */ + +int +ignore_add (char *mask, int type) +{ + struct ignore *ig = 0; + int change_only = FALSE; + + /* first check if it's already ignored */ + ig = ignore_exists (mask); + if (ig) + change_only = TRUE; + + if (!change_only) + ig = malloc (sizeof (struct ignore)); + + if (!ig) + return 0; + + ig->mask = strdup (mask); + ig->type = type; + + if (!change_only) + ignore_list = g_slist_prepend (ignore_list, ig); + fe_ignore_update (1); + + if (change_only) + return 2; + + return 1; +} + +void +ignore_showlist (session *sess) +{ + struct ignore *ig; + GSList *list = ignore_list; + char tbuf[256]; + int i = 0; + + EMIT_SIGNAL (XP_TE_IGNOREHEADER, sess, 0, 0, 0, 0, 0); + + while (list) + { + ig = list->data; + i++; + + snprintf (tbuf, sizeof (tbuf), " %-25s ", ig->mask); + if (ig->type & IG_PRIV) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_NOTI) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_CHAN) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_CTCP) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_DCC) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_INVI) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_UNIG) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + strcat (tbuf, "\n"); + PrintText (sess, tbuf); + /*EMIT_SIGNAL (XP_TE_IGNORELIST, sess, ig->mask, 0, 0, 0, 0); */ + /* use this later, when TE's support 7 args */ + list = list->next; + } + + if (!i) + EMIT_SIGNAL (XP_TE_IGNOREEMPTY, sess, 0, 0, 0, 0, 0); + + EMIT_SIGNAL (XP_TE_IGNOREFOOTER, sess, 0, 0, 0, 0, 0); +} + +/* ignore_del() + + * one of the args must be NULL, use mask OR *ig, not both + * + */ + +int +ignore_del (char *mask, struct ignore *ig) +{ + if (!ig) + { + GSList *list = ignore_list; + + while (list) + { + ig = (struct ignore *) list->data; + if (!rfc_casecmp (ig->mask, mask)) + break; + list = list->next; + ig = 0; + } + } + if (ig) + { + ignore_list = g_slist_remove (ignore_list, ig); + free (ig->mask); + free (ig); + fe_ignore_update (1); + return TRUE; + } + return FALSE; +} + +/* check if a msg should be ignored by browsing our ignore list */ + +int +ignore_check (char *host, int type) +{ + struct ignore *ig; + GSList *list = ignore_list; + + /* check if there's an UNIGNORE first, they take precendance. */ + while (list) + { + ig = (struct ignore *) list->data; + if (ig->type & IG_UNIG) + { + if (ig->type & type) + { + if (match (ig->mask, host)) + return FALSE; + } + } + list = list->next; + } + + list = ignore_list; + while (list) + { + ig = (struct ignore *) list->data; + + if (ig->type & type) + { + if (match (ig->mask, host)) + { + ignored_total++; + if (type & IG_PRIV) + ignored_priv++; + if (type & IG_NOTI) + ignored_noti++; + if (type & IG_CHAN) + ignored_chan++; + if (type & IG_CTCP) + ignored_ctcp++; + if (type & IG_INVI) + ignored_invi++; + fe_ignore_update (2); + return TRUE; + } + } + list = list->next; + } + + return FALSE; +} + +static char * +ignore_read_next_entry (char *my_cfg, struct ignore *ignore) +{ + char tbuf[1024]; + + /* Casting to char * done below just to satisfy compiler */ + + if (my_cfg) + { + my_cfg = cfg_get_str (my_cfg, "mask", tbuf, sizeof (tbuf)); + if (!my_cfg) + return NULL; + ignore->mask = strdup (tbuf); + } + if (my_cfg) + { + my_cfg = cfg_get_str (my_cfg, "type", tbuf, sizeof (tbuf)); + ignore->type = atoi (tbuf); + } + return my_cfg; +} + +void +ignore_load () +{ + struct ignore *ignore; + struct stat st; + char *cfg, *my_cfg; + int fh, i; + + fh = xchat_open_file ("ignore.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + fstat (fh, &st); + if (st.st_size) + { + cfg = malloc (st.st_size + 1); + cfg[0] = '\0'; + i = read (fh, cfg, st.st_size); + if (i >= 0) + cfg[i] = '\0'; + my_cfg = cfg; + while (my_cfg) + { + ignore = malloc (sizeof (struct ignore)); + memset (ignore, 0, sizeof (struct ignore)); + if ((my_cfg = ignore_read_next_entry (my_cfg, ignore))) + ignore_list = g_slist_prepend (ignore_list, ignore); + else + free (ignore); + } + free (cfg); + } + close (fh); + } +} + +void +ignore_save () +{ + char buf[1024]; + int fh; + GSList *temp = ignore_list; + struct ignore *ig; + + fh = xchat_open_file ("ignore.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + while (temp) + { + ig = (struct ignore *) temp->data; + if (!(ig->type & IG_NOSAVE)) + { + snprintf (buf, sizeof (buf), "mask = %s\ntype = %d\n\n", + ig->mask, ig->type); + write (fh, buf, strlen (buf)); + } + temp = temp->next; + } + close (fh); + } + +} + +static gboolean +flood_autodialog_timeout (gpointer data) +{ + prefs.autodialog = 1; + return FALSE; +} + +int +flood_check (char *nick, char *ip, server *serv, session *sess, int what) /*0=ctcp 1=priv */ +{ + /* + serv + int ctcp_counter; + time_t ctcp_last_time; + prefs + unsigned int ctcp_number_limit; + unsigned int ctcp_time_limit; + */ + char buf[512]; + char real_ip[132]; + int i; + time_t current_time; + current_time = time (NULL); + + if (what == 0) + { + if (serv->ctcp_last_time == 0) /*first ctcp in this server */ + { + serv->ctcp_last_time = time (NULL); + serv->ctcp_counter++; + } else + { + if (difftime (current_time, serv->ctcp_last_time) < prefs.ctcp_time_limit) /*if we got the ctcp in the seconds limit */ + { + serv->ctcp_counter++; + if (serv->ctcp_counter == prefs.ctcp_number_limit) /*if we reached the maximun numbers of ctcp in the seconds limits */ + { + serv->ctcp_last_time = current_time; /*we got the flood, restore all the vars for next one */ + serv->ctcp_counter = 0; + for (i = 0; i < 128; i++) + if (ip[i] == '@') + break; + snprintf (real_ip, sizeof (real_ip), "*!*%s", &ip[i]); + /*ignore_add (char *mask, int priv, int noti, int chan, + int ctcp, int invi, int unignore, int no_save) */ + + snprintf (buf, sizeof (buf), + _("You are being CTCP flooded from %s, ignoring %s\n"), + nick, real_ip); + PrintText (sess, buf); + + /*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */ + ignore_add (real_ip, IG_CTCP); + return 0; + } + } + } + } else + { + if (serv->msg_last_time == 0) + { + serv->msg_last_time = time (NULL); + serv->ctcp_counter++; + } else + { + if (difftime (current_time, serv->msg_last_time) < + prefs.msg_time_limit) + { + serv->msg_counter++; + if (serv->msg_counter == prefs.msg_number_limit) /*if we reached the maximun numbers of ctcp in the seconds limits */ + { + snprintf (buf, sizeof (buf), + _("You are being MSG flooded from %s, setting gui_auto_open_dialog OFF.\n"), + ip); + PrintText (sess, buf); + serv->msg_last_time = current_time; /*we got the flood, restore all the vars for next one */ + serv->msg_counter = 0; + /*ignore_add (char *mask, int priv, int noti, int chan, + int ctcp, int invi, int unignore, int no_save) */ + + if (prefs.autodialog) + { + /*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */ + prefs.autodialog = 0; + /* turn it back on in 30 secs */ + fe_timeout_add (30000, flood_autodialog_timeout, NULL); + } + return 0; + } + } + } + } + return 1; +} + diff --git a/src/common/ignore.h b/src/common/ignore.h new file mode 100644 index 00000000..3a971a86 --- /dev/null +++ b/src/common/ignore.h @@ -0,0 +1,38 @@ +#ifndef XCHAT_IGNORE_H +#define XCHAT_IGNORE_H + +extern GSList *ignore_list; + +extern int ignored_ctcp; +extern int ignored_priv; +extern int ignored_chan; +extern int ignored_noti; +extern int ignored_invi; + +#define IG_PRIV 1 +#define IG_NOTI 2 +#define IG_CHAN 4 +#define IG_CTCP 8 +#define IG_INVI 16 +#define IG_UNIG 32 +#define IG_NOSAVE 64 +#define IG_DCC 128 + +struct ignore +{ + char *mask; + unsigned int type; /* one of more of IG_* ORed together */ +}; + +struct ignore *ignore_exists (char *mask); +int ignore_add (char *mask, int type); +void ignore_showlist (session *sess); +int ignore_del (char *mask, struct ignore *ig); +int ignore_check (char *mask, int type); +void ignore_load (void); +void ignore_save (void); +void ignore_gui_open (void); +void ignore_gui_update (int level); +int flood_check (char *nick, char *ip, server *serv, session *sess, int what); + +#endif diff --git a/src/common/inbound.c b/src/common/inbound.c new file mode 100644 index 00000000..ec7dd9d0 --- /dev/null +++ b/src/common/inbound.c @@ -0,0 +1,1336 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <time.h> + +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#include "xchat.h" +#include "util.h" +#include "ignore.h" +#include "fe.h" +#include "modes.h" +#include "notify.h" +#include "outbound.h" +#include "inbound.h" +#include "server.h" +#include "servlist.h" +#include "text.h" +#include "ctcp.h" +#include "plugin.h" +#include "xchatc.h" + + +void +clear_channel (session *sess) +{ + if (sess->channel[0]) + strcpy (sess->waitchannel, sess->channel); + sess->channel[0] = 0; + sess->doing_who = FALSE; + sess->done_away_check = FALSE; + + log_close (sess); + + if (sess->current_modes) + { + free (sess->current_modes); + sess->current_modes = NULL; + } + + if (sess->mode_timeout_tag) + { + fe_timeout_remove (sess->mode_timeout_tag); + sess->mode_timeout_tag = 0; + } + + fe_clear_channel (sess); + userlist_clear (sess); + fe_set_nonchannel (sess, FALSE); + fe_set_title (sess); +} + +void +set_topic (session *sess, char *topic, char *stripped_topic) +{ + if (sess->topic) + free (sess->topic); + sess->topic = strdup (stripped_topic); + fe_set_topic (sess, topic, stripped_topic); +} + +static session * +find_session_from_nick (char *nick, server *serv) +{ + session *sess; + GSList *list = sess_list; + + sess = find_dialog (serv, nick); + if (sess) + return sess; + + if (serv->front_session) + { + if (userlist_find (serv->front_session, nick)) + return serv->front_session; + } + + if (current_sess && current_sess->server == serv) + { + if (userlist_find (current_sess, nick)) + return current_sess; + } + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (userlist_find (sess, nick)) + return sess; + } + list = list->next; + } + return 0; +} + +static session * +inbound_open_dialog (server *serv, char *from) +{ + session *sess; + + sess = new_ircwindow (serv, from, SESS_DIALOG, 0); + /* for playing sounds */ + EMIT_SIGNAL (XP_TE_OPENDIALOG, sess, NULL, NULL, NULL, NULL, 0); + + return sess; +} + +static void +inbound_make_idtext (server *serv, char *idtext, int max, int id) +{ + idtext[0] = 0; + if (serv->have_idmsg) + { + if (id) + { + safe_strcpy (idtext, prefs.irc_id_ytext, max); + } else + { + safe_strcpy (idtext, prefs.irc_id_ntext, max); + } + /* convert codes like %C,%U to the proper ones */ + check_special_chars (idtext, TRUE); + } +} + +void +inbound_privmsg (server *serv, char *from, char *ip, char *text, int id) +{ + session *sess; + char idtext[64]; + + sess = find_dialog (serv, from); + + if (sess || prefs.autodialog) + { + /*0=ctcp 1=priv will set autodialog=0 here is flud detected */ + if (!sess) + { + if (flood_check (from, ip, serv, current_sess, 1)) + /* Create a dialog session */ + sess = inbound_open_dialog (serv, from); + else + sess = serv->server_session; + if (!sess) + return; /* ?? */ + } + + if (ip && ip[0]) + { + if (prefs.logging && sess->logfd != -1 && + (!sess->topic || strcmp(sess->topic, ip))) + { + char tbuf[1024]; + snprintf (tbuf, sizeof (tbuf), "[%s has address %s]\n", from, ip); + write (sess->logfd, tbuf, strlen (tbuf)); + } + set_topic (sess, ip, ip); + } + inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, id); + return; + } + + inbound_make_idtext (serv, idtext, sizeof (idtext), id); + + sess = find_session_from_nick (from, serv); + if (!sess) + { + sess = serv->front_session; + EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0); + return; + } + + if (sess->type == SESS_DIALOG) + EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0); + else + EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0); +} + +/* used for Alerts section. Masks can be separated by commas and spaces. */ + +gboolean +alert_match_word (char *word, char *masks) +{ + char *p = masks; + char endchar; + int res; + + if (masks[0] == 0) + return FALSE; + + while (1) + { + /* if it's a 0, space or comma, the word has ended. */ + if (*p == 0 || *p == ' ' || *p == ',') + { + endchar = *p; + *p = 0; + res = match (masks, word); + *p = endchar; + + if (res) + return TRUE; /* yes, matched! */ + + masks = p + 1; + if (*p == 0) + return FALSE; + } + p++; + } +} + +gboolean +alert_match_text (char *text, char *masks) +{ + unsigned char *p = text; + unsigned char endchar; + int res; + + if (masks[0] == 0) + return FALSE; + + while (1) + { + if (*p >= '0' && *p <= '9') + { + p++; + continue; + } + + /* if it's RFC1459 <special>, it can be inside a word */ + switch (*p) + { + case '-': case '[': case ']': case '\\': + case '`': case '^': case '{': case '}': + case '_': case '|': + p++; + continue; + } + + /* if it's a 0, space or comma, the word has ended. */ + if (*p == 0 || *p == ' ' || *p == ',' || + /* if it's anything BUT a letter, the word has ended. */ + (!g_unichar_isalpha (g_utf8_get_char (p)))) + { + endchar = *p; + *p = 0; + res = alert_match_word (text, masks); + *p = endchar; + + if (res) + return TRUE; /* yes, matched! */ + + text = p + g_utf8_skip [p[0]]; + if (*p == 0) + return FALSE; + } + + p += g_utf8_skip [p[0]]; + } +} + +static int +is_hilight (char *from, char *text, session *sess, server *serv) +{ + if (alert_match_word (from, prefs.irc_no_hilight)) + return 0; + + text = strip_color (text, -1, STRIP_ALL); + + if (alert_match_text (text, serv->nick) || + alert_match_text (text, prefs.irc_extra_hilight) || + alert_match_word (from, prefs.irc_nick_hilight)) + { + g_free (text); + if (sess != current_tab) + sess->nick_said = TRUE; + fe_set_hilight (sess); + return 1; + } + + g_free (text); + return 0; +} + +void +inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id) +{ + session *def = sess; + server *serv = sess->server; + struct User *user; + char nickchar[2] = "\000"; + char idtext[64]; + int privaction = FALSE; + + if (!fromme) + { + if (is_channel (serv, chan)) + { + sess = find_channel (serv, chan); + } else + { + /* it's a private action! */ + privaction = TRUE; + /* find a dialog tab for it */ + sess = find_dialog (serv, from); + /* if non found, open a new one */ + if (!sess && prefs.autodialog) + { + /* but only if it wouldn't flood */ + if (flood_check (from, ip, serv, current_sess, 1)) + sess = inbound_open_dialog (serv, from); + else + sess = serv->server_session; + } + if (!sess) + { + sess = find_session_from_nick (from, serv); + /* still not good? */ + if (!sess) + sess = serv->front_session; + } + } + } + + if (!sess) + sess = def; + + if (sess != current_tab) + { + if (fromme) + { + sess->msg_said = FALSE; + sess->new_data = TRUE; + } else + { + sess->msg_said = TRUE; + sess->new_data = FALSE; + } + } + + user = userlist_find (sess, from); + if (user) + { + nickchar[0] = user->prefix[0]; + user->lasttalk = time (0); + } + + inbound_make_idtext (serv, idtext, sizeof (idtext), id); + + if (!fromme && !privaction) + { + if (is_hilight (from, text, sess, serv)) + { + EMIT_SIGNAL (XP_TE_HCHANACTION, sess, from, text, nickchar, idtext, 0); + return; + } + } + + if (fromme) + EMIT_SIGNAL (XP_TE_UACTION, sess, from, text, nickchar, idtext, 0); + else if (!privaction) + EMIT_SIGNAL (XP_TE_CHANACTION, sess, from, text, nickchar, idtext, 0); + else if (sess->type == SESS_DIALOG) + EMIT_SIGNAL (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL, 0); + else + EMIT_SIGNAL (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, 0); +} + +void +inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id) +{ + struct User *user; + int hilight = FALSE; + char nickchar[2] = "\000"; + char idtext[64]; + + if (!sess) + { + if (chan) + { + sess = find_channel (serv, chan); + if (!sess && !is_channel (serv, chan)) + sess = find_dialog (serv, chan); + } else + { + sess = find_dialog (serv, from); + } + if (!sess) + return; + } + + if (sess != current_tab) + { + sess->msg_said = TRUE; + sess->new_data = FALSE; + } + + user = userlist_find (sess, from); + if (user) + { + nickchar[0] = user->prefix[0]; + user->lasttalk = time (0); + } + + if (fromme) + { + if (prefs.auto_unmark_away && serv->is_away) + sess->server->p_set_back (sess->server); + EMIT_SIGNAL (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL, 0); + return; + } + + inbound_make_idtext (serv, idtext, sizeof (idtext), id); + + if (is_hilight (from, text, sess, serv)) + hilight = TRUE; + + if (sess->type == SESS_DIALOG) + EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0); + else if (hilight) + EMIT_SIGNAL (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext, 0); + else + EMIT_SIGNAL (XP_TE_CHANMSG, sess, from, text, nickchar, idtext, 0); +} + +void +inbound_newnick (server *serv, char *nick, char *newnick, int quiet) +{ + int me = FALSE; + session *sess; + GSList *list = sess_list; + + if (!serv->p_cmp (nick, serv->nick)) + { + me = TRUE; + safe_strcpy (serv->nick, newnick, NICKLEN); + } + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (userlist_change (sess, nick, newnick) || (me && sess->type == SESS_SERVER)) + { + if (!quiet) + { + if (me) + EMIT_SIGNAL (XP_TE_UCHANGENICK, sess, nick, newnick, NULL, + NULL, 0); + else + EMIT_SIGNAL (XP_TE_CHANGENICK, sess, nick, newnick, NULL, + NULL, 0); + } + } + if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick)) + { + safe_strcpy (sess->channel, newnick, CHANLEN); + fe_set_channel (sess); + } + fe_set_title (sess); + } + list = list->next; + } + + dcc_change_nick (serv, nick, newnick); + + if (me) + fe_set_nick (serv, newnick); +} + +/* find a "<none>" tab */ +static session * +find_unused_session (server *serv) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->type == SESS_CHANNEL && sess->channel[0] == 0 && + sess->server == serv) + { + if (sess->waitchannel[0] == 0) + return sess; + } + list = list->next; + } + return 0; +} + +static session * +find_session_from_waitchannel (char *chan, struct server *serv) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == serv && sess->channel[0] == 0 && sess->type == SESS_CHANNEL) + { + if (!serv->p_cmp (chan, sess->waitchannel)) + return sess; + } + list = list->next; + } + return 0; +} + +void +inbound_ujoin (server *serv, char *chan, char *nick, char *ip) +{ + session *sess; + + /* already joined? probably a bnc */ + sess = find_channel (serv, chan); + if (!sess) + { + /* see if a window is waiting to join this channel */ + sess = find_session_from_waitchannel (chan, serv); + if (!sess) + { + /* find a "<none>" tab and use that */ + sess = find_unused_session (serv); + if (!sess) + /* last resort, open a new tab/window */ + sess = new_ircwindow (serv, chan, SESS_CHANNEL, 1); + } + } + + safe_strcpy (sess->channel, chan, CHANLEN); + + fe_set_channel (sess); + fe_set_title (sess); + fe_set_nonchannel (sess, TRUE); + userlist_clear (sess); + + log_open_or_close (sess); + + sess->waitchannel[0] = 0; + sess->ignore_date = TRUE; + sess->ignore_mode = TRUE; + sess->ignore_names = TRUE; + sess->end_of_names = FALSE; + + /* sends a MODE */ + serv->p_join_info (sess->server, chan); + + EMIT_SIGNAL (XP_TE_UJOIN, sess, nick, chan, ip, NULL, 0); + + if (prefs.userhost) + { + /* sends WHO #channel */ + serv->p_user_list (sess->server, chan); + sess->doing_who = TRUE; + } +} + +void +inbound_ukick (server *serv, char *chan, char *kicker, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + EMIT_SIGNAL (XP_TE_UKICK, sess, serv->nick, chan, kicker, reason, 0); + clear_channel (sess); + if (prefs.autorejoin) + { + serv->p_join (serv, chan, sess->channelkey); + safe_strcpy (sess->waitchannel, chan, CHANLEN); + } + } +} + +void +inbound_upart (server *serv, char *chan, char *ip, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + if (*reason) + EMIT_SIGNAL (XP_TE_UPARTREASON, sess, serv->nick, ip, chan, reason, + 0); + else + EMIT_SIGNAL (XP_TE_UPART, sess, serv->nick, ip, chan, NULL, 0); + clear_channel (sess); + } +} + +void +inbound_nameslist (server *serv, char *chan, char *names) +{ + session *sess; + char name[NICKLEN]; + int pos = 0; + + sess = find_channel (serv, chan); + if (!sess) + { + EMIT_SIGNAL (XP_TE_USERSONCHAN, serv->server_session, chan, names, NULL, + NULL, 0); + return; + } + if (!sess->ignore_names) + EMIT_SIGNAL (XP_TE_USERSONCHAN, sess, chan, names, NULL, NULL, 0); + + if (sess->end_of_names) + { + sess->end_of_names = FALSE; + userlist_clear (sess); + } + + while (1) + { + switch (*names) + { + case 0: + name[pos] = 0; + if (pos != 0) + userlist_add (sess, name, 0); + return; + case ' ': + name[pos] = 0; + pos = 0; + userlist_add (sess, name, 0); + break; + default: + name[pos] = *names; + if (pos < (NICKLEN-1)) + pos++; + } + names++; + } +} + +void +inbound_topic (server *serv, char *chan, char *topic_text) +{ + session *sess = find_channel (serv, chan); + char *stripped_topic; + + if (sess) + { + stripped_topic = strip_color (topic_text, -1, STRIP_ALL); + set_topic (sess, topic_text, stripped_topic); + g_free (stripped_topic); + } else + sess = serv->server_session; + + EMIT_SIGNAL (XP_TE_TOPIC, sess, chan, topic_text, NULL, NULL, 0); +} + +void +inbound_topicnew (server *serv, char *nick, char *chan, char *topic) +{ + session *sess; + char *stripped_topic; + + sess = find_channel (serv, chan); + if (sess) + { + stripped_topic = strip_color (topic, -1, STRIP_ALL); + set_topic (sess, topic, stripped_topic); + g_free (stripped_topic); + EMIT_SIGNAL (XP_TE_NEWTOPIC, sess, nick, topic, chan, NULL, 0); + } +} + +void +inbound_join (server *serv, char *chan, char *user, char *ip) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + EMIT_SIGNAL (XP_TE_JOIN, sess, user, chan, ip, NULL, 0); + userlist_add (sess, user, ip); + } +} + +void +inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + EMIT_SIGNAL (XP_TE_KICK, sess, kicker, user, chan, reason, 0); + userlist_remove (sess, user); + } +} + +void +inbound_part (server *serv, char *chan, char *user, char *ip, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + if (*reason) + EMIT_SIGNAL (XP_TE_PARTREASON, sess, user, ip, chan, reason, 0); + else + EMIT_SIGNAL (XP_TE_PART, sess, user, ip, chan, NULL, 0); + userlist_remove (sess, user); + } +} + +void +inbound_topictime (server *serv, char *chan, char *nick, time_t stamp) +{ + char *tim = ctime (&stamp); + session *sess = find_channel (serv, chan); + + if (!sess) + sess = serv->server_session; + + tim[24] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_TOPICDATE, sess, chan, nick, tim, NULL, 0); +} + +void +inbound_quit (server *serv, char *nick, char *ip, char *reason) +{ + GSList *list = sess_list; + session *sess; + int was_on_front_session = FALSE; + + while (list) + { + sess = (session *) list->data; + if (sess->server == serv) + { + if (sess == current_sess) + was_on_front_session = TRUE; + if (userlist_remove (sess, nick)) + { + EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0); + } else if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick)) + { + EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0); + } + } + list = list->next; + } + + notify_set_offline (serv, nick, was_on_front_session); +} + +void +inbound_ping_reply (session *sess, char *timestring, char *from) +{ + unsigned long tim, nowtim, dif; + int lag = 0; + char outbuf[64]; + + if (strncmp (timestring, "LAG", 3) == 0) + { + timestring += 3; + lag = 1; + } + + tim = strtoul (timestring, NULL, 10); + nowtim = make_ping_time (); + dif = nowtim - tim; + + sess->server->ping_recv = time (0); + + if (lag) + { + sess->server->lag_sent = 0; + sess->server->lag = dif / 1000; + fe_set_lag (sess->server, dif / 100000); + return; + } + + if (atol (timestring) == 0) + { + if (sess->server->lag_sent) + sess->server->lag_sent = 0; + else + EMIT_SIGNAL (XP_TE_PINGREP, sess, from, "?", NULL, NULL, 0); + } else + { + snprintf (outbuf, sizeof (outbuf), "%ld.%ld%ld", dif / 1000000, (dif / 100000) % 10, dif % 10); + EMIT_SIGNAL (XP_TE_PINGREP, sess, from, outbuf, NULL, NULL, 0); + } +} + +static session * +find_session_from_type (int type, server *serv) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = list->data; + if (sess->type == type && serv == sess->server) + return sess; + list = list->next; + } + return 0; +} + +void +inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id) +{ + char *po,*ptr=to; + session *sess = 0; + int server_notice = FALSE; + + if (is_channel (serv, ptr)) + sess = find_channel (serv, ptr); + + if (!sess && ptr[0] == '@') + { + ptr++; + sess = find_channel (serv, ptr); + } + + if (!sess && ptr[0] == '%') + { + ptr++; + sess = find_channel (serv, ptr); + } + + if (!sess && ptr[0] == '+') + { + ptr++; + sess = find_channel (serv, ptr); + } + + if (strcmp (nick, ip) == 0) + server_notice = TRUE; + + if (!sess) + { + ptr = 0; + if (prefs.notices_tabs) + { + int stype = server_notice ? SESS_SNOTICES : SESS_NOTICES; + sess = find_session_from_type (stype, serv); + if (!sess) + { + if (stype == SESS_NOTICES) + sess = new_ircwindow (serv, "(notices)", SESS_NOTICES, 0); + else + sess = new_ircwindow (serv, "(snotices)", SESS_SNOTICES, 0); + fe_set_channel (sess); + fe_set_title (sess); + fe_set_nonchannel (sess, FALSE); + userlist_clear (sess); + log_open_or_close (sess); + } + /* Avoid redundancy with some Undernet notices */ + if (!strncmp (msg, "*** Notice -- ", 14)) + msg += 14; + } else + { + /* paranoia check */ + if (msg[0] == '[' && (!serv->have_idmsg || id)) + { + /* guess where chanserv meant to post this -sigh- */ + if (!strcasecmp (nick, "ChanServ") && !find_dialog (serv, nick)) + { + char *dest = strdup (msg + 1); + char *end = strchr (dest, ']'); + if (end) + { + *end = 0; + sess = find_channel (serv, dest); + } + free (dest); + } + } + if (!sess) + sess = find_session_from_nick (nick, serv); + } + if (!sess) + { + if (server_notice) + sess = serv->server_session; + else + sess = serv->front_session; + } + } + + if (msg[0] == 1) + { + msg++; + if (!strncmp (msg, "PING", 4)) + { + inbound_ping_reply (sess, msg + 5, nick); + return; + } + } + po = strchr (msg, '\001'); + if (po) + po[0] = 0; + + if (server_notice) + EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, msg, nick, NULL, NULL, 0); + else if (ptr) + EMIT_SIGNAL (XP_TE_CHANNOTICE, sess, nick, to, msg, NULL, 0); + else + EMIT_SIGNAL (XP_TE_NOTICE, sess, nick, msg, NULL, NULL, 0); +} + +void +inbound_away (server *serv, char *nick, char *msg) +{ + struct away_msg *away = server_away_find_message (serv, nick); + session *sess = NULL; + GSList *list; + + if (away && !strcmp (msg, away->message)) /* Seen the msg before? */ + { + if (prefs.show_away_once && !serv->inside_whois) + return; + } else + { + server_away_save_message (serv, nick, msg); + } + + if (prefs.irc_whois_front) + sess = serv->front_session; + else + { + if (!serv->inside_whois) + sess = find_session_from_nick (nick, serv); + if (!sess) + sess = serv->server_session; + } + + /* possibly hide the output */ + if (!serv->inside_whois || !serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS5, sess, nick, msg, NULL, NULL, 0); + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->server == serv) + userlist_set_away (sess, nick, TRUE); + list = list->next; + } +} + +int +inbound_nameslist_end (server *serv, char *chan) +{ + session *sess; + GSList *list; + + if (!strcmp (chan, "*")) + { + list = sess_list; + while (list) + { + sess = list->data; + if (sess->server == serv) + { + sess->end_of_names = TRUE; + sess->ignore_names = FALSE; + } + list = list->next; + } + return TRUE; + } + sess = find_channel (serv, chan); + if (sess) + { + sess->end_of_names = TRUE; + sess->ignore_names = FALSE; + return TRUE; + } + return FALSE; +} + +static gboolean +check_autojoin_channels (server *serv) +{ + char *po; + session *sess; + GSList *list = sess_list; + int i = 0; + GSList *channels, *keys; + + /* shouldnt really happen, the io tag is destroyed in server.c */ + if (!is_server (serv)) + return FALSE; + + /* send auto join list */ + if (serv->autojoin) + { + joinlist_split (serv->autojoin, &channels, &keys); + serv->p_join_list (serv, channels, keys); + joinlist_free (channels, keys); + + free (serv->autojoin); + serv->autojoin = NULL; + } + + /* this is really only for re-connects when you + * join channels not in the auto-join list. */ + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (sess->willjoinchannel[0] != 0) + { + strcpy (sess->waitchannel, sess->willjoinchannel); + sess->willjoinchannel[0] = 0; + serv->p_join (serv, sess->waitchannel, sess->channelkey); + po = strchr (sess->waitchannel, ','); + if (po) + *po = 0; + po = strchr (sess->waitchannel, ' '); + if (po) + *po = 0; + i++; + } + } + list = list->next; + } + serv->joindelay_tag = 0; + fe_server_event (serv, FE_SE_LOGGEDIN, i); + return FALSE; +} + +void +inbound_next_nick (session *sess, char *nick) +{ + char *newnick; + server *serv = sess->server; + ircnet *net; + + serv->nickcount++; + + switch (serv->nickcount) + { + case 2: + newnick = prefs.nick2; + net = serv->network; + /* use network specific "Second choice"? */ + if (net && !(net->flags & FLAG_USE_GLOBAL) && net->nick2) + newnick = net->nick2; + serv->p_change_nick (serv, newnick); + EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL, 0); + break; + + case 3: + serv->p_change_nick (serv, prefs.nick3); + EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, prefs.nick3, NULL, NULL, 0); + break; + + default: + EMIT_SIGNAL (XP_TE_NICKFAIL, sess, NULL, NULL, NULL, NULL, 0); + } +} + +void +do_dns (session *sess, char *nick, char *host) +{ + char *po; + char tbuf[1024]; + + po = strrchr (host, '@'); + if (po) + host = po + 1; + EMIT_SIGNAL (XP_TE_RESOLVINGUSER, sess, nick, host, NULL, NULL, 0); + snprintf (tbuf, sizeof (tbuf), "exec -d %s %s", prefs.dnsprogram, host); + handle_command (sess, tbuf, FALSE); +} + +static void +set_default_modes (server *serv) +{ + char modes[8]; + + modes[0] = '+'; + modes[1] = '\0'; + + if (prefs.wallops) + strcat (modes, "w"); + if (prefs.servernotice) + strcat (modes, "s"); + if (prefs.invisible) + strcat (modes, "i"); + + if (modes[1] != '\0') + { + serv->p_mode (serv, serv->nick, modes); + } +} + +void +inbound_login_start (session *sess, char *nick, char *servname) +{ + inbound_newnick (sess->server, sess->server->nick, nick, TRUE); + server_set_name (sess->server, servname); + if (sess->type == SESS_SERVER) + log_open_or_close (sess); + /* reset our away status */ + if (sess->server->reconnect_away) + { + handle_command (sess->server->server_session, "away", FALSE); + sess->server->reconnect_away = FALSE; + } +} + +static void +inbound_set_all_away_status (server *serv, char *nick, unsigned int status) +{ + GSList *list; + session *sess; + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->server == serv) + userlist_set_away (sess, nick, status); + list = list->next; + } +} + +void +inbound_uaway (server *serv) +{ + serv->is_away = TRUE; + serv->away_time = time (NULL); + fe_set_away (serv); + + inbound_set_all_away_status (serv, serv->nick, 1); +} + +void +inbound_uback (server *serv) +{ + serv->is_away = FALSE; + serv->reconnect_away = FALSE; + fe_set_away (serv); + + inbound_set_all_away_status (serv, serv->nick, 0); +} + +void +inbound_foundip (session *sess, char *ip) +{ + struct hostent *HostAddr; + + HostAddr = gethostbyname (ip); + if (HostAddr) + { + prefs.dcc_ip = ((struct in_addr *) HostAddr->h_addr)->s_addr; + EMIT_SIGNAL (XP_TE_FOUNDIP, sess, + inet_ntoa (*((struct in_addr *) HostAddr->h_addr)), + NULL, NULL, NULL, 0); + } +} + +void +inbound_user_info_start (session *sess, char *nick) +{ + /* set away to FALSE now, 301 may turn it back on */ + inbound_set_all_away_status (sess->server, nick, 0); +} + +/* reporting new information found about this user. chan may be NULL. + * away may be 0xff to indicate UNKNOWN. */ + +void +inbound_user_info (session *sess, char *chan, char *user, char *host, + char *servname, char *nick, char *realname, + unsigned int away) +{ + server *serv = sess->server; + session *who_sess; + GSList *list; + char *uhost = NULL; + + if (user && host) + { + uhost = g_malloc (strlen (user) + strlen (host) + 2); + sprintf (uhost, "%s@%s", user, host); + } + + if (chan) + { + who_sess = find_channel (serv, chan); + if (who_sess) + userlist_add_hostname (who_sess, nick, uhost, realname, servname, away); + else + { + if (serv->doing_dns && nick && host) + do_dns (sess, nick, host); + } + } + else + { + /* came from WHOIS, not channel specific */ + for (list = sess_list; list; list = list->next) + { + sess = list->data; + if (sess->type == SESS_CHANNEL && sess->server == serv) + { + userlist_add_hostname (sess, nick, uhost, realname, servname, away); + } + } + } + + g_free (uhost); +} + +int +inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption) +{ + char *time_str = ctime (&stamp); + server *serv = sess->server; + + time_str[19] = 0; /* get rid of the \n */ + if (stamp == 0) + time_str = ""; + + sess = find_channel (serv, chan); + if (!sess) + { + sess = serv->front_session; + goto nowindow; + } + + if (!fe_is_banwindow (sess)) + { +nowindow: + /* let proto-irc.c do the 'goto def' for exemptions */ + if (is_exemption) + return FALSE; + + EMIT_SIGNAL (XP_TE_BANLIST, sess, chan, mask, banner, time_str, 0); + return TRUE; + } + + fe_add_ban_list (sess, mask, banner, time_str, is_exemption); + return TRUE; +} + +/* execute 1 end-of-motd command */ + +static int +inbound_exec_eom_cmd (char *str, void *sess) +{ + handle_command (sess, (str[0] == '/') ? str + 1 : str, TRUE); + return 1; +} + +void +inbound_login_end (session *sess, char *text) +{ + server *serv = sess->server; + + if (!serv->end_of_motd) + { + if (prefs.ip_from_server && serv->use_who) + { + serv->skip_next_userhost = TRUE; + serv->p_get_ip_uh (serv, serv->nick); /* sends USERHOST mynick */ + } + set_default_modes (serv); + + if (serv->network) + { + /* there may be more than 1, separated by \n */ + if (((ircnet *)serv->network)->command) + token_foreach (((ircnet *)serv->network)->command, '\n', + inbound_exec_eom_cmd, sess); + + /* send nickserv password */ + if (((ircnet *)serv->network)->nickserv) + serv->p_ns_identify (serv, ((ircnet *)serv->network)->nickserv); + } + + /* send JOIN now or wait? */ + if (serv->network && ((ircnet *)serv->network)->nickserv && + prefs.irc_join_delay) + serv->joindelay_tag = fe_timeout_add (prefs.irc_join_delay * 1000, + check_autojoin_channels, serv); + else + check_autojoin_channels (serv); + if (serv->supports_watch) + notify_send_watches (serv); + serv->end_of_motd = TRUE; + } + if (prefs.skipmotd && !serv->motd_skipped) + { + serv->motd_skipped = TRUE; + EMIT_SIGNAL (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL, + NULL, NULL, 0); + return; + } + EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, + NULL, NULL, 0); +} + +void +inbound_identified (server *serv) /* 'MODE +e MYSELF' on freenode */ +{ + if (serv->joindelay_tag) + { + /* stop waiting, just auto JOIN now */ + fe_timeout_remove (serv->joindelay_tag); + serv->joindelay_tag = 0; + check_autojoin_channels (serv); + } +} diff --git a/src/common/inbound.h b/src/common/inbound.h new file mode 100644 index 00000000..b972981f --- /dev/null +++ b/src/common/inbound.h @@ -0,0 +1,39 @@ +#ifndef XCHAT_INBOUND_H +#define XCHAT_INBOUND_H + +void inbound_next_nick (session *sess, char *nick); +void inbound_uback (server *serv); +void inbound_uaway (server *serv); +void inbound_part (server *serv, char *chan, char *user, char *ip, char *reason); +void inbound_upart (server *serv, char *chan, char *ip, char *reason); +void inbound_ukick (server *serv, char *chan, char *kicker, char *reason); +void inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason); +void inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id); +void inbound_quit (server *serv, char *nick, char *ip, char *reason); +void inbound_topicnew (server *serv, char *nick, char *chan, char *topic); +void inbound_join (server *serv, char *chan, char *user, char *ip); +void inbound_ujoin (server *serv, char *chan, char *nick, char *ip); +void inbound_topictime (server *serv, char *chan, char *nick, time_t stamp); +void inbound_topic (server *serv, char *chan, char *topic_text); +void inbound_user_info_start (session *sess, char *nick); +void inbound_user_info (session *sess, char *chan, char *user, char *host, char *servname, char *nick, char *realname, unsigned int away); +void inbound_foundip (session *sess, char *ip); +int inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption); +void inbound_ping_reply (session *sess, char *timestring, char *from); +void inbound_nameslist (server *serv, char *chan, char *names); +int inbound_nameslist_end (server *serv, char *chan); +void inbound_away (server *serv, char *nick, char *msg); +void inbound_login_start (session *sess, char *nick, char *servname); +void inbound_login_end (session *sess, char *text); +void inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id); +void clear_channel (session *sess); +void set_topic (session *sess, char *topic, char *stripped_topic); +void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id); +void inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id); +void inbound_newnick (server *serv, char *nick, char *newnick, int quiet); +void do_dns (session *sess, char *nick, char *host); +void inbound_identified (server *serv); +gboolean alert_match_word (char *word, char *masks); +gboolean alert_match_text (char *text, char *masks); + +#endif diff --git a/src/common/inet.h b/src/common/inet.h new file mode 100644 index 00000000..b420c9c6 --- /dev/null +++ b/src/common/inet.h @@ -0,0 +1,43 @@ +/* include stuff for internet */ + +#ifndef WIN32 + +#ifdef WANTSOCKET +#include <sys/types.h> +#include <sys/socket.h> +#endif +#ifdef WANTARPA +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#ifdef WANTDNS +#include <netdb.h> +#endif +#define closesocket close +#define set_blocking(sok) fcntl(sok, F_SETFL, 0) +#define set_nonblocking(sok) fcntl(sok, F_SETFL, O_NONBLOCK) +#define would_block() (errno == EAGAIN || errno == EWOULDBLOCK) +#define sock_error() (errno) + +#else + +#ifdef USE_IPV6 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <tpipv6.h> +#else +#include <winsock.h> +#endif + +#define set_blocking(sok) { \ + unsigned long zero = 0; \ + ioctlsocket (sok, FIONBIO, &zero); \ + } +#define set_nonblocking(sok) { \ + unsigned long one = 1; \ + ioctlsocket (sok, FIONBIO, &one); \ + } +#define would_block() (WSAGetLastError() == WSAEWOULDBLOCK) +#define sock_error WSAGetLastError + +#endif diff --git a/src/common/make-te.c b/src/common/make-te.c new file mode 100644 index 00000000..ed87df3d --- /dev/null +++ b/src/common/make-te.c @@ -0,0 +1,58 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +int main() +{ + char name[512]; + char num[512]; + char help[512]; + char def[512]; + char args[512]; + char buf[512]; + char *defines[512]; + int i = 0, max; + + printf("/* this file is auto generated, edit textevents.in instead! */\n\nconst struct text_event te[] = {\n"); + while(fgets(name, sizeof(name), stdin)) + { + name[strlen(name)-1] = 0; + fgets(num, sizeof(num), stdin); + num[strlen(num)-1] = 0; + fgets(help, sizeof(help), stdin); + help[strlen(help)-1] = 0; + fgets(def, sizeof(def), stdin); + def[strlen(def)-1] = 0; + fgets(args, sizeof(args), stdin); + args[strlen(args)-1] = 0; + fgets(buf, sizeof(buf), stdin); + + if (args[0] == 'n') + printf("\n{\"%s\", %s, %d, \n\"%s\"},\n", + name, help, atoi(args+1) | 128, def); + else + printf("\n{\"%s\", %s, %d, \nN_(\"%s\")},\n", + name, help, atoi(args), def); + defines[i] = strdup (num); + i++; + } + + printf("};\n"); + + fprintf(stderr, "/* this file is auto generated, edit textevents.in instead! */\n\nenum\n{\n"); + max = i; + i = 0; + while (i < max) + { + if (i + 1 < max) + { + fprintf(stderr, "\t%s,\t\t%s,\n", defines[i], defines[i+1]); + i++; + } else + fprintf(stderr, "\t%s,\n", defines[i]); + i++; + } + fprintf(stderr, "\tNUM_XP\n};\n"); + + return 0; +} diff --git a/src/common/modes.c b/src/common/modes.c new file mode 100644 index 00000000..1acf7f54 --- /dev/null +++ b/src/common/modes.c @@ -0,0 +1,836 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib.h> +#include <glib/gprintf.h> + +#include "xchat.h" +#include "xchatc.h" +#include "modes.h" +#include "server.h" +#include "text.h" +#include "fe.h" +#include "util.h" +#include "inbound.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +typedef struct +{ + server *serv; + char *op; + char *deop; + char *voice; + char *devoice; +} mode_run; + +static int is_prefix_char (server * serv, char c); +static void record_chan_mode (session *sess, char sign, char mode, char *arg); +static char *mode_cat (char *str, char *addition); +static void handle_single_mode (mode_run *mr, char sign, char mode, char *nick, char *chan, char *arg, int quiet, int is_324); +static int mode_has_arg (server *serv, char sign, char mode); +static void mode_print_grouped (session *sess, char *nick, mode_run *mr); +static int mode_chanmode_type (server * serv, char mode); + + +/* word[] - list of nicks. + wpos - index into word[]. Where nicks really start. + end - index into word[]. Last entry plus one. + sign - a char, e.g. '+' or '-' + mode - a mode, e.g. 'o' or 'v' */ +void +send_channel_modes (session *sess, char *tbuf, char *word[], int wpos, + int end, char sign, char mode, int modes_per_line) +{ + int usable_modes, orig_len, len, wlen, i, max; + server *serv = sess->server; + + /* sanity check. IRC RFC says three per line. */ + if (serv->modes_per_line < 3) + serv->modes_per_line = 3; + if (modes_per_line < 1) + modes_per_line = serv->modes_per_line; + + /* RFC max, minus length of "MODE %s " and "\r\n" and 1 +/- sign */ + /* 512 - 6 - 2 - 1 - strlen(chan) */ + max = 503 - strlen (sess->channel); + + while (wpos < end) + { + tbuf[0] = '\0'; + orig_len = len = 0; + + /* we'll need this many modechars too */ + len += modes_per_line; + + /* how many can we fit? */ + for (i = 0; i < modes_per_line; i++) + { + /* no more nicks left? */ + if (wpos + i >= end) + break; + wlen = strlen (word[wpos + i]) + 1; + if (wlen + len > max) + break; + len += wlen; /* length of our whole string so far */ + } + if (i < 1) + return; + usable_modes = i; /* this is how many we'll send on this line */ + + /* add the +/-modemodemodemode */ + len = orig_len; + tbuf[len] = sign; + len++; + for (i = 0; i < usable_modes; i++) + { + tbuf[len] = mode; + len++; + } + tbuf[len] = 0; /* null terminate for the strcat() to work */ + + /* add all the nicknames */ + for (i = 0; i < usable_modes; i++) + { + strcat (tbuf, " "); + strcat (tbuf, word[wpos + i]); + } + serv->p_mode (serv, sess->channel, tbuf); + + wpos += usable_modes; + } +} + +/* does 'chan' have a valid prefix? e.g. # or & */ + +int +is_channel (server * serv, char *chan) +{ + if (strchr (serv->chantypes, chan[0])) + return 1; + return 0; +} + +/* is the given char a valid nick mode char? e.g. @ or + */ + +static int +is_prefix_char (server * serv, char c) +{ + int pos = 0; + char *np = serv->nick_prefixes; + + while (np[0]) + { + if (np[0] == c) + return pos; + pos++; + np++; + } + + if (serv->bad_prefix) + { + if (strchr (serv->bad_nick_prefixes, c)) + /* valid prefix char, but mode unknown */ + return -2; + } + + return -1; +} + +/* returns '@' for ops etc... */ + +char +get_nick_prefix (server * serv, unsigned int access) +{ + int pos; + char c; + + for (pos = 0; pos < USERACCESS_SIZE; pos++) + { + c = serv->nick_prefixes[pos]; + if (c == 0) + break; + if (access & (1 << pos)) + return c; + } + + return 0; +} + +/* returns the access bitfield for a nickname. E.g. + @nick would return 000010 in binary + %nick would return 000100 in binary + +nick would return 001000 in binary */ + +unsigned int +nick_access (server * serv, char *nick, int *modechars) +{ + int i; + unsigned int access = 0; + char *orig = nick; + + while (*nick) + { + i = is_prefix_char (serv, *nick); + if (i == -1) + break; + + /* -2 == valid prefix char, but mode unknown */ + if (i != -2) + access |= (1 << i); + + nick++; + } + + *modechars = nick - orig; + + return access; +} + +/* returns the access number for a particular mode. e.g. + mode 'a' returns 0 + mode 'o' returns 1 + mode 'h' returns 2 + mode 'v' returns 3 + Also puts the nick-prefix-char in 'prefix' */ + +int +mode_access (server * serv, char mode, char *prefix) +{ + int pos = 0; + + while (serv->nick_modes[pos]) + { + if (serv->nick_modes[pos] == mode) + { + *prefix = serv->nick_prefixes[pos]; + return pos; + } + pos++; + } + + *prefix = 0; + + return -1; +} + +static void +record_chan_mode (session *sess, char sign, char mode, char *arg) +{ + /* Somebody needed to acutally update sess->current_modes, needed to + play nice with bouncers, and less mode calls. Also keeps modes up + to date for scripts */ + server *serv = sess->server; + GString *current = g_string_new(sess->current_modes); + gint mode_pos = -1; + gchar *current_char = current->str; + gint modes_length; + gint argument_num = 0; + gint argument_offset = 0; + gint argument_length = 0; + int i = 0; + gchar *arguments_start; + + /* find out if the mode currently exists */ + arguments_start = g_strstr_len(current->str , -1, " "); + if (arguments_start) { + modes_length = arguments_start - current->str; + } + else { + modes_length = current->len; + /* set this to the end of the modes */ + arguments_start = current->str + current->len; + } + + while (mode_pos == -1 && i < modes_length) + { + if (*current_char == mode) + { + mode_pos = i; + } + else + { + i++; + current_char++; + } + } + + /* if the mode currently exists and has an arg, need to know where + * (including leading space) */ + if (mode_pos != -1 && mode_has_arg(serv, '+', mode)) + { + current_char = current->str; + + i = 0; + while (i <= mode_pos) + { + if (mode_has_arg(serv, '+', *current_char)) + argument_num++; + current_char++; + i++; + } + + /* check through arguments for where to start */ + current_char = arguments_start; + i = 0; + while (i < argument_num && *current_char != '\0') + { + if (*current_char == ' ') + i++; + if (i != argument_num) + current_char++; + } + argument_offset = current_char - current->str; + + /* how long the existing argument is for this key + * important for malloc and strncpy */ + if (i == argument_num) + { + argument_length++; + current_char++; + while (*current_char != '\0' && *current_char != ' ') + { + argument_length++; + current_char++; + } + } + } + + /* two cases, adding and removing a mode, handled differently */ + if (sign == '+') + { + if (mode_pos != -1) + { + /* if it already exists, only need to do something (change) + * if there should be a param */ + if (mode_has_arg(serv, sign, mode)) + { + /* leave the old space there */ + current = g_string_erase(current, argument_offset+1, argument_length-1); + current = g_string_insert(current, argument_offset+1, arg); + + free(sess->current_modes); + sess->current_modes = g_string_free(current, FALSE); + } + } + /* mode wasn't there before */ + else + { + /* insert the new mode character */ + current = g_string_insert_c(current, modes_length, mode); + + /* add the argument, with space if there is one */ + if (mode_has_arg(serv, sign, mode)) + { + current = g_string_append_c(current, ' '); + current = g_string_append(current, arg); + } + + free(sess->current_modes); + sess->current_modes = g_string_free(current, FALSE); + } + } + else if (sign == '-' && mode_pos != -1) + { + /* remove the argument first if it has one*/ + if (mode_has_arg(serv, '+', mode)) + current = g_string_erase(current, argument_offset, argument_length); + + /* remove the mode character */ + current = g_string_erase(current, mode_pos, 1); + + free(sess->current_modes); + sess->current_modes = g_string_free(current, FALSE); + } +} + +static char * +mode_cat (char *str, char *addition) +{ + int len; + + if (str) + { + len = strlen (str) + strlen (addition) + 2; + str = realloc (str, len); + strcat (str, " "); + strcat (str, addition); + } else + { + str = strdup (addition); + } + + return str; +} + +/* handle one mode, e.g. + handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */ + +static void +handle_single_mode (mode_run *mr, char sign, char mode, char *nick, + char *chan, char *arg, int quiet, int is_324) +{ + session *sess; + server *serv = mr->serv; + char outbuf[4]; + + outbuf[0] = sign; + outbuf[1] = 0; + outbuf[2] = mode; + outbuf[3] = 0; + + sess = find_channel (serv, chan); + if (!sess || !is_channel (serv, chan)) + { + /* got modes for a chan we're not in! probably nickmode +isw etc */ + sess = serv->front_session; + goto genmode; + } + + /* is this a nick mode? */ + if (strchr (serv->nick_modes, mode)) + { + /* update the user in the userlist */ + userlist_update_mode (sess, /*nickname */ arg, mode, sign); + } else + { + if (!is_324 && !sess->ignore_mode && mode_chanmode_type(serv, mode) >= 1) + record_chan_mode (sess, sign, mode, arg); + } + + switch (sign) + { + case '+': + switch (mode) + { + case 'k': + safe_strcpy (sess->channelkey, arg, sizeof (sess->channelkey)); + fe_update_channel_key (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANSETKEY, sess, nick, arg, NULL, NULL, 0); + return; + case 'l': + sess->limit = atoi (arg); + fe_update_channel_limit (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANSETLIMIT, sess, nick, arg, NULL, NULL, 0); + return; + case 'o': + if (!quiet) + mr->op = mode_cat (mr->op, arg); + return; + case 'h': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANHOP, sess, nick, arg, NULL, NULL, 0); + return; + case 'v': + if (!quiet) + mr->voice = mode_cat (mr->voice, arg); + return; + case 'b': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANBAN, sess, nick, arg, NULL, NULL, 0); + return; + case 'e': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANEXEMPT, sess, nick, arg, NULL, NULL, 0); + return; + case 'I': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANINVITE, sess, nick, arg, NULL, NULL, 0); + return; + } + break; + case '-': + switch (mode) + { + case 'k': + sess->channelkey[0] = 0; + fe_update_channel_key (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMKEY, sess, nick, NULL, NULL, NULL, 0); + return; + case 'l': + sess->limit = 0; + fe_update_channel_limit (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMLIMIT, sess, nick, NULL, NULL, NULL, 0); + return; + case 'o': + if (!quiet) + mr->deop = mode_cat (mr->deop, arg); + return; + case 'h': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANDEHOP, sess, nick, arg, NULL, NULL, 0); + return; + case 'v': + if (!quiet) + mr->devoice = mode_cat (mr->devoice, arg); + return; + case 'b': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANUNBAN, sess, nick, arg, NULL, NULL, 0); + return; + case 'e': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMEXEMPT, sess, nick, arg, NULL, NULL, 0); + return; + case 'I': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMINVITE, sess, nick, arg, NULL, NULL, 0); + return; + } + } + + fe_update_mode_buttons (sess, mode, sign); + + genmode: + /* Received umode +e. If we're waiting to send JOIN then send now! */ + if (mode == 'e' && sign == '+' && !serv->p_cmp (chan, serv->nick)) + inbound_identified (serv); + + if (!quiet) + { + if (*arg) + { + char *buf = malloc (strlen (chan) + strlen (arg) + 2); + sprintf (buf, "%s %s", chan, arg); + EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, buf, 0); + free (buf); + } else + EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, chan, 0); + } +} + +/* does this mode have an arg? like +b +l +o */ + +static int +mode_has_arg (server * serv, char sign, char mode) +{ + int type; + + /* if it's a nickmode, it must have an arg */ + if (strchr (serv->nick_modes, mode)) + return 1; + + type = mode_chanmode_type (serv, mode); + switch (type) + { + case 0: /* type A */ + case 1: /* type B */ + return 1; + case 2: /* type C */ + if (sign == '+') + return 1; + case 3: /* type D */ + return 0; + default: + return 0; + } + +} + +/* what type of chanmode is it? -1 for not in chanmode */ +static int +mode_chanmode_type (server * serv, char mode) +{ + /* see what numeric 005 CHANMODES=xxx said */ + char *cm = serv->chanmodes; + int type = 0; + int found = 0; + + while (*cm && !found) + { + if (*cm == ',') + { + type++; + } else if (*cm == mode) + { + found = 1; + } + cm++; + } + if (found) + return type; + /* not found? -1 */ + else + return -1; +} + +static void +mode_print_grouped (session *sess, char *nick, mode_run *mr) +{ + /* print all the grouped Op/Deops */ + if (mr->op) + { + EMIT_SIGNAL (XP_TE_CHANOP, sess, nick, mr->op, NULL, NULL, 0); + free (mr->op); + mr->op = NULL; + } + + if (mr->deop) + { + EMIT_SIGNAL (XP_TE_CHANDEOP, sess, nick, mr->deop, NULL, NULL, 0); + free (mr->deop); + mr->deop = NULL; + } + + if (mr->voice) + { + EMIT_SIGNAL (XP_TE_CHANVOICE, sess, nick, mr->voice, NULL, NULL, 0); + free (mr->voice); + mr->voice = NULL; + } + + if (mr->devoice) + { + EMIT_SIGNAL (XP_TE_CHANDEVOICE, sess, nick, mr->devoice, NULL, NULL, 0); + free (mr->devoice); + mr->devoice = NULL; + } +} + + +/* handle a MODE or numeric 324 from server */ + +void +handle_mode (server * serv, char *word[], char *word_eol[], + char *nick, int numeric_324) +{ + session *sess; + char *chan; + char *modes; + char *argstr; + char sign; + int len; + int arg; + int i, num_args; + int num_modes; + int offset = 3; + int all_modes_have_args = FALSE; + int using_front_tab = FALSE; + mode_run mr; + + mr.serv = serv; + mr.op = mr.deop = mr.voice = mr.devoice = NULL; + + /* numeric 324 has everything 1 word later (as opposed to MODE) */ + if (numeric_324) + offset++; + + chan = word[offset]; + modes = word[offset + 1]; + if (*modes == ':') + modes++; + + if (*modes == 0) + return; /* beyondirc's blank modes */ + + sess = find_channel (serv, chan); + if (!sess) + { + sess = serv->front_session; + using_front_tab = TRUE; + } + /* remove trailing space */ + len = strlen (word_eol[offset]) - 1; + if (word_eol[offset][len] == ' ') + word_eol[offset][len] = 0; + + if (prefs.raw_modes && !numeric_324) + EMIT_SIGNAL (XP_TE_RAWMODES, sess, nick, word_eol[offset], 0, 0, 0); + + if (numeric_324 && !using_front_tab) + { + if (sess->current_modes) + free (sess->current_modes); + sess->current_modes = strdup (word_eol[offset+1]); + } + + sign = *modes; + modes++; + arg = 1; + + /* count the number of arguments (e.g. after the -o+v) */ + num_args = 0; + i = 1; + while ((i + offset + 1) < PDIWORDS) + { + i++; + if (!(*word[i + offset])) + break; + num_args++; + } + + /* count the number of modes (without the -/+ chars */ + num_modes = 0; + i = 0; + while (i < strlen (modes)) + { + if (modes[i] != '+' && modes[i] != '-') + num_modes++; + i++; + } + + if (num_args == num_modes) + all_modes_have_args = TRUE; + + while (*modes) + { + switch (*modes) + { + case '-': + case '+': + /* print all the grouped Op/Deops */ + mode_print_grouped (sess, nick, &mr); + sign = *modes; + break; + default: + argstr = ""; + if ((all_modes_have_args || mode_has_arg (serv, sign, *modes)) && arg < (num_args+1)) + { + arg++; + argstr = word[arg + offset]; + } + handle_single_mode (&mr, sign, *modes, nick, chan, + argstr, numeric_324 || prefs.raw_modes, + numeric_324); + } + + modes++; + } + + /* update the title at the end, now that the mode update is internal now */ + if (!using_front_tab) + fe_set_title (sess); + + /* print all the grouped Op/Deops */ + mode_print_grouped (sess, nick, &mr); +} + +/* handle the 005 numeric */ + +void +inbound_005 (server * serv, char *word[]) +{ + int w; + char *pre; + + w = 4; /* start at the 4th word */ + while (w < PDIWORDS && *word[w]) + { + if (strncmp (word[w], "MODES=", 6) == 0) + { + serv->modes_per_line = atoi (word[w] + 6); + } else if (strncmp (word[w], "CHANTYPES=", 10) == 0) + { + free (serv->chantypes); + serv->chantypes = strdup (word[w] + 10); + } else if (strncmp (word[w], "CHANMODES=", 10) == 0) + { + free (serv->chanmodes); + serv->chanmodes = strdup (word[w] + 10); + } else if (strncmp (word[w], "PREFIX=", 7) == 0) + { + pre = strchr (word[w] + 7, ')'); + if (pre) + { + pre[0] = 0; /* NULL out the ')' */ + free (serv->nick_prefixes); + free (serv->nick_modes); + serv->nick_prefixes = strdup (pre + 1); + serv->nick_modes = strdup (word[w] + 8); + } else + { + /* bad! some ircds don't give us the modes. */ + /* in this case, we use it only to strip /NAMES */ + serv->bad_prefix = TRUE; + if (serv->bad_nick_prefixes) + free (serv->bad_nick_prefixes); + serv->bad_nick_prefixes = strdup (word[w] + 7); + } + } else if (strncmp (word[w], "WATCH=", 6) == 0) + { + serv->supports_watch = TRUE; + } else if (strncmp (word[w], "NETWORK=", 8) == 0) + { +/* if (serv->networkname) + free (serv->networkname); + serv->networkname = strdup (word[w] + 8);*/ + + if (serv->server_session->type == SESS_SERVER) + { + safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN); + fe_set_channel (serv->server_session); + } + + /* use /NICKSERV */ + if (strcasecmp (word[w] + 8, "RusNet") == 0) + serv->nickservtype = 1; + else if (strcasecmp (word[w] + 8, "UniBG") == 0) + serv->nickservtype = 3; + else if (strcasecmp (word[w] + 8, "QuakeNet") == 0) + serv->nickservtype = 4; + + } else if (strncmp (word[w], "CASEMAPPING=", 12) == 0) + { + if (strcmp (word[w] + 12, "ascii") == 0) /* bahamut */ + serv->p_cmp = (void *)strcasecmp; + } else if (strncmp (word[w], "CHARSET=", 8) == 0) + { + if (strcasecmp (word[w] + 8, "UTF-8") == 0) + { + server_set_encoding (serv, "UTF-8"); + } + } else if (strcmp (word[w], "NAMESX") == 0) + { + /* 12345678901234567 */ + tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17); + } else if (strcmp (word[w], "WHOX") == 0) + { + serv->have_whox = TRUE; + } else if (strcmp (word[w], "CAPAB") == 0) + { + serv->have_capab = TRUE; + /* 12345678901234567890 */ + tcp_send_len (serv, "CAPAB IDENTIFY-MSG\r\n", 20); + /* now wait for numeric 290 */ + } else if (strcmp (word[w], "EXCEPTS") == 0) + { +#ifndef WIN32 + serv->have_except = TRUE; +#endif + } else if (strncmp (word[w], "ELIST=", 6) == 0) + { + /* supports LIST >< min/max user counts? */ + if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u')) + serv->use_listargs = TRUE; + } + + w++; + } +} diff --git a/src/common/modes.h b/src/common/modes.h new file mode 100644 index 00000000..3f9c4a72 --- /dev/null +++ b/src/common/modes.h @@ -0,0 +1,12 @@ +#ifndef XCHAT_MODES_H +#define XCHAT_MODES_H + +int is_channel (server *serv, char *chan); +char get_nick_prefix (server *serv, unsigned int access); +unsigned int nick_access (server *serv, char *nick, int *modechars); +int mode_access (server *serv, char mode, char *prefix); +void inbound_005 (server *serv, char *word[]); +void handle_mode (server *serv, char *word[], char *word_eol[], char *nick, int numeric_324); +void send_channel_modes (session *sess, char *tbuf, char *word[], int start, int end, char sign, char mode, int modes_per_line); + +#endif diff --git a/src/common/msproxy.c b/src/common/msproxy.c new file mode 100644 index 00000000..9c85394d --- /dev/null +++ b/src/common/msproxy.c @@ -0,0 +1,467 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 + * + * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru> + * based on Dante source code + * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + * Inferno Nettverk A/S, Norway. All rights reserved. + */ + +/*#define DEBUG_MSPROXY*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#include "inet.h" + +#include "xchat.h" +#include "network.h" +#include "xchatc.h" +#include "server.h" +#include "msproxy.h" + + +#ifdef USE_MSPROXY +#include <ntlm.h> + +static int +send_msprequest(s, state, request, end) + int s; + struct msproxy_state_t *state; + struct msproxy_request_t *request; + char *end; +{ + ssize_t w; + size_t l; + + request->magic25 = htonl(MSPROXY_VERSION); + request->serverack = state->seq_recv; + /* don't start incrementing sequence until we are acking packet #2. */ + request->sequence = (unsigned char)(request->serverack >= 2 ? state->seq_sent + 1 : 0); + + memcpy(request->RWSP, "RWSP", sizeof(request->RWSP)); + + l = end - (char *)request; + /* all requests must be atleast MSPROXY_MINLENGTH it seems. */ + if (l < MSPROXY_MINLENGTH) { + bzero(end, (size_t)(MSPROXY_MINLENGTH - l)); + l = MSPROXY_MINLENGTH; + } + + if ((w = send(s, request, l, 0)) != l) { +#ifdef DEBUG_MSPROXY + printf ("send_msprequest(): send() failed (%d bytes sent instead of %d\n", w, l); + perror ("Error is"); +#endif + return -1; + } + state->seq_sent = request->sequence; + + return w; +} + +static int +recv_mspresponse(s, state, response) + int s; + struct msproxy_state_t *state; + struct msproxy_response_t *response; +{ + ssize_t r; + + do { + if ((r = recv (s, response, sizeof (*response), 0)) < MSPROXY_MINLENGTH) { +#ifdef DEBUG_MSPROXY + printf ("recv_mspresponse(): expected to read atleast %d, read %d\n", MSPROXY_MINLENGTH, r); +#endif + return -1; + } + if (state->seq_recv == 0) + break; /* not started incrementing yet. */ +#ifdef DEBUG_MSPROXY + if (response->sequence == state->seq_recv) + printf ("seq_recv: %d, dup response, seqnumber: 0x%x\n", state->seq_recv, response->sequence); +#endif + } while (response->sequence == state->seq_recv); + + state->seq_recv = response->sequence; + + return r; +} + +int +traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound) +{ + struct msproxy_request_t req; + struct msproxy_response_t res; + char *data, *p; + char hostname[NT_MAXNAMELEN]; + char ntdomain[NT_MAXNAMELEN]; + char challenge[8]; + netstore *ns_client; + int clientport; + guint32 destaddr; + guint32 flags; + + if (!prefs.proxy_auth || !prefs.proxy_user[0] || !prefs.proxy_pass[0] ) + return 1; + + /* MS proxy protocol implementation currently doesn't support IPv6 */ + destaddr = net_getsockaddr_v4 (ns_proxy); + if (!destaddr) + return 1; + + state->seq_recv = 0; + state->seq_sent = 0; + +#ifdef DEBUG_MSPROXY + printf ("Connecting to %s:%d via MS proxy\n", serverAddr, port); +#endif + + gethostname (hostname, NT_MAXNAMELEN); + p = strchr (hostname, '.'); + if (p) + *p = '\0'; + + bzero (&req, sizeof(req)); + req.clientid = htonl(0x0a000000); /* Initial client ID is always 0x0a */ + req.command = htons(MSPROXY_HELLO); /* HELLO command */ + req.packet.hello.magic5 = htons(0x4b00); /* Fill in magic values */ + req.packet.hello.magic10 = htons(0x1400); + req.packet.hello.magic15 = htons(0x0400); + req.packet.hello.magic20 = htons(0x5704); + req.packet.hello.magic25 = htons(0x0004); + req.packet.hello.magic30 = htons(0x0100); + req.packet.hello.magic35 = htons(0x4a02); + req.packet.hello.magic40 = htons(0x3000); + req.packet.hello.magic45 = htons(0x4400); + req.packet.hello.magic50 = htons(0x3900); + data = req.packet.hello.data; + strcpy (data, prefs.proxy_user); /* Append a username */ + data += strlen (prefs.proxy_user)+2; /* +2 automatically creates second empty string */ + strcpy (data, MSPROXY_EXECUTABLE); /* Append an application name */ + data += strlen (MSPROXY_EXECUTABLE)+1; + strcpy (data, hostname); /* Append a hostname */ + data += strlen (hostname)+1; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (strcmp(res.RWSP, "RWSP") != 0) { +#ifdef DEBUG_MSPROXY + printf ("Received mailformed packet (no RWSP signature)\n"); +#endif + return 1; + } + + if (ntohs(res.command) >> 8 != 0x10) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 10??, is %x", ntohs(res.command)); +#endif + return 1; + } + + state->clientid = htonl(rand()); + state->serverid = res.serverid; + +#ifdef DEBUG_MSPROXY + printf ("clientid: 0x%x, serverid: 0x%0x\n", state->clientid, state->serverid); + printf ("packet #2\n"); +#endif + + /* almost identical. */ + req.clientid = state->clientid; + req.serverid = state->serverid; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (res.serverid != state->serverid) { +#ifdef DEBUG_MSPROXY + printf ("expected serverid = 0x%x, is 0x%x\n",state->serverid, res.serverid); +#endif + return 1; + } + + if (res.sequence != 0x01) { +#ifdef DEBUG_MSPROXY + printf ("expected res.sequence = 0x01, is 0x%x\n", res.sequence); +#endif + return 1; + } + + if (ntohs(res.command) != MSPROXY_USERINFO_ACK) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command)); +#endif + return 1; + } + +#ifdef DEBUG_MSPROXY + printf ("packet #3\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_AUTHENTICATE); + memcpy(req.packet.auth.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP")); + req.packet.auth.bindaddr = htonl(0x02000000); + req.packet.auth.msgtype = htonl(0x01000000); + /* NTLM flags: 0x80000000 Negotiate LAN Manager key + 0x10000000 Negotiate sign + 0x04000000 Request target + 0x02000000 Negotiate OEM + 0x00800000 Always sign + 0x00020000 Negotiate NTLM + */ + req.packet.auth.flags = htonl(0x06020000); + + if (send_msprequest(sok, state, &req, &req.packet.auth.data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (res.serverid != state->serverid) { +#ifdef DEBUG_MSPROXY + printf ("expected serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid); +#endif + return 1; + } + + if (ntohs(res.command) != MSPROXY_AUTHENTICATE_ACK) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_AUTHENTICATE_ACK, ntohs(res.command)); +#endif + return 1; + } + + flags = res.packet.auth.flags & htonl(0x00020000); /* Remember if the server supports NTLM */ + memcpy(challenge, &res.packet.auth.challenge, sizeof(challenge)); + memcpy(ntdomain, &res.packet.auth.NTLMSSP[res.packet.auth.target.offset], res.packet.auth.target.len); + ntdomain[res.packet.auth.target.len] = 0; + +#ifdef DEBUG_MSPROXY + printf ("ntdomain: \"%s\"\n", ntdomain); + printf ("packet #4\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_AUTHENTICATE_2); /* Authentication response */ + req.packet.auth2.magic3 = htons(0x0200); /* Something */ + memcpy(req.packet.auth2.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP")); /* Start of NTLM message */ + req.packet.auth2.msgtype = htonl(0x03000000); /* Message type 2 */ + req.packet.auth2.flags = flags | htonl(0x02000000); /* Choose authentication method */ + data = req.packet.auth2.data; + if (flags) { + req.packet.auth2.lm_resp.len = 0; /* We are here if NTLM is supported, */ + req.packet.auth2.lm_resp.alloc = 0; /* Do not fill in insecure LM response */ + req.packet.auth2.lm_resp.offset = data - req.packet.auth2.NTLMSSP; + req.packet.auth2.ntlm_resp.len = 24; /* Fill in NTLM response security buffer */ + req.packet.auth2.ntlm_resp.alloc = 24; + req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP; + ntlm_smb_nt_encrypt(prefs.proxy_pass, challenge, data); /* Append an NTLM response */ + data += 24; + } else { + req.packet.auth2.lm_resp.len = 24; /* Fill in LM response security buffer */ + req.packet.auth2.lm_resp.alloc = 24; + req.packet.auth2.lm_resp.offset = data - req.packet.auth2.NTLMSSP; + ntlm_smb_encrypt(prefs.proxy_pass, challenge, data); /* Append an LM response */ + data += 24; + req.packet.auth2.ntlm_resp.len = 0; /* NTLM response is empty */ + req.packet.auth2.ntlm_resp.alloc = 0; + req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP; + } + req.packet.auth2.ntdomain_buf.len = strlen(ntdomain); /* Domain name */ + req.packet.auth2.ntdomain_buf.alloc = req.packet.auth2.ntdomain_buf.len; + req.packet.auth2.ntdomain_buf.offset = data - req.packet.auth2.NTLMSSP; + strcpy(data, ntdomain); + data += req.packet.auth2.ntdomain_buf.len; + req.packet.auth2.username_buf.len = strlen(prefs.proxy_user); /* Username */ + req.packet.auth2.username_buf.alloc = req.packet.auth2.username_buf.len; + req.packet.auth2.username_buf.offset = data - req.packet.auth2.NTLMSSP; + strcpy(data, prefs.proxy_user); + data += req.packet.auth2.username_buf.len; + req.packet.auth2.clienthost_buf.len = strlen(hostname); /* Hostname */ + req.packet.auth2.clienthost_buf.alloc = req.packet.auth2.clienthost_buf.len; + req.packet.auth2.clienthost_buf.offset = data - req.packet.auth2.NTLMSSP; + strcpy(data, hostname); + data += req.packet.auth2.clienthost_buf.len; + req.packet.auth2.sessionkey_buf.len = 0; /* Session key (we don't use it) */ + req.packet.auth2.sessionkey_buf.alloc = 0; + req.packet.auth2.sessionkey_buf.offset = data - req.packet.auth2.NTLMSSP; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (res.serverid != state->serverid) { +#ifdef DEBUG_MSPROXY + printf ("expected res.serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid); +#endif + return 1; + } + + if (res.clientack != 0x01) { +#ifdef DEBUG_MSPROXY + printf ("expected res.clientack = 0x01, is 0x%x\n", res.clientack); +#endif + return 1; + } + + if (ntohs(res.command) >> 8 != 0x47) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 47??, is 0x%x\n", ntohs(res.command)); +#endif + return 1; + } + + if (ntohs(res.command) == MSPROXY_AUTHENTICATE_2_NAK) { +#ifdef DEBUG_MSPROXY + printf ("Authentication failed\n"); +#endif + return -1; + } + +#ifdef DEBUG_MSPROXY + printf ("packet #5\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_CONNECT); + req.packet.connect.magic2 = htons(0x0200); + req.packet.connect.magic6 = htons(0x0200); + req.packet.connect.destport = htons(port); + req.packet.connect.destaddr = destaddr; + data = req.packet.connect.executable; + strcpy(data, MSPROXY_EXECUTABLE); + data += strlen(MSPROXY_EXECUTABLE) + 1; + + /* + * need to tell server what port we will connect from, so we bind our sockets. + */ + ns_client = net_store_new (); + if (!bound) { + net_store_fill_any (ns_client); + net_bind(ns_client, csok4, csok6); +#ifdef DEBUG_MSPROXY + perror ("bind() result"); +#endif + } + clientport = net_getsockport(csok4, csok6); + if (clientport == -1) { +#ifdef DEBUG_MSPROXY + printf ("Unable to obtain source port\n"); +#endif + return 1; + } + req.packet.connect.srcport = clientport; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (ntohs(res.command) != MSPROXY_CONNECT_ACK) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 0x%x, is 0x%x\n",MSPROXY_CONNECT_ACK, ntohs(res.command)); +#endif + return 1; + } + + net_store_fill_v4 (ns_client, res.packet.connect.clientaddr, res.packet.connect.clientport); + +#ifdef DEBUG_MSPROXY + printf ("Connecting...\n"); +#endif + if (net_connect (ns_client, csok4, csok6, csok) != 0) { +#ifdef DEBUG_MSPROXY + printf ("Failed to connect to port %d\n", htons(res.packet.connect.clientport)); +#endif + net_store_destroy (ns_client); + return 1; + } + net_store_destroy (ns_client); +#ifdef DEBUG_MSPROXY + printf ("packet #6\n"); +#endif + + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_USERINFO_ACK); + + if (send_msprequest(sok, state, &req, req.packet.connack.data) == -1) + return 1; + + return 0; +} + +void +msproxy_keepalive (void) +{ + server *serv; + GSList *list = serv_list; + struct msproxy_request_t req; + struct msproxy_response_t res; + + while (list) + { + serv = list->data; + if (serv->connected && (serv->proxy_sok != -1)) + { +#ifdef DEBUG_MSPROXY + printf ("sending MS proxy keepalive packet\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = serv->msp_state.clientid; + req.serverid = serv->msp_state.serverid; + req.command = htons(MSPROXY_HELLO); + + if (send_msprequest(serv->proxy_sok, &serv->msp_state, &req, req.packet.hello.data) == -1) + continue; + + recv_mspresponse(serv->proxy_sok, &serv->msp_state, &res); + +#ifdef DEBUG_MSPROXY + if (ntohs(res.command) != MSPROXY_USERINFO_ACK) + printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command)); +#endif + } + list = list->next; + } +} + +#endif diff --git a/src/common/msproxy.h b/src/common/msproxy.h new file mode 100644 index 00000000..d37c81c5 --- /dev/null +++ b/src/common/msproxy.h @@ -0,0 +1,257 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 + * + * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru> + * based on Dante source code + * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + * Inferno Nettverk A/S, Norway. All rights reserved. + */ + +#include "network.h" + +#define MSPROXY_EXECUTABLE "xchat.exe" /* This probably can be used for access control on the server side */ + +#define MSPROXY_MINLENGTH 172 /* minimum length of packet. */ +#define NT_MAXNAMELEN 17 /* maximum name length (domain etc), comes from NetBIOS */ +#define MSPROXY_VERSION 0x00010200 /* MS Proxy v2 ? */ + +/* Commands / responses */ +#define MSPROXY_HELLO 0x0500 /* packet 1 from client. */ +#define MSPROXY_HELLO_ACK 0x1000 /* packet 1 from server. */ + +#define MSPROXY_USERINFO_ACK 0x0400 /* packet 2 from server. */ + +#define MSPROXY_AUTHENTICATE 0x4700 /* authentication request */ +#define MSPROXY_AUTHENTICATE_ACK 0x4714 /* authentication challenge */ + +#define MSPROXY_AUTHENTICATE_2 0x4701 /* authentication response */ +#define MSPROXY_AUTHENTICATE_2_ACK 0x4715 /* authentication passed */ +#define MSPROXY_AUTHENTICATE_2_NAK 0x4716 /* authentication failure */ + +#define MSPROXY_CONNECT 0x071e /* connect request. */ +#define MSPROXY_CONNECT_ACK 0x0703 /* connect request accepted. */ + +#pragma pack(1) + +struct ntlm_buffer { + guint16 len; + guint16 alloc; + guint32 offset; +}; + +struct msproxy_request_t { + guint32 clientid; /* 1-4 */ + guint32 magic25; /* 5-8 */ + guint32 serverid; /* 9-12 */ + unsigned char serverack; /* 13: ack of last server packet */ + char pad10[3]; /* 14-16 */ + unsigned char sequence; /* 17: sequence # of this packet. */ + char pad11[7]; /* 18-24 */ + char RWSP[4]; /* 25-28: 0x52,0x57,0x53,0x50 */ + char pad15[8]; /* 29-36 */ + guint16 command; /* 37-38 */ + + /* packet specifics start at 39. */ + union { + struct { + char pad1[18]; /* 39-56 */ + guint16 magic3; /* 57-58 */ + char pad3[114]; /* 59-172 */ + guint16 magic5; /* 173-174: 0x4b, 0x00 */ + char pad5[2]; /* 175-176 */ + guint16 magic10; /* 177-178: 0x14, 0x00 */ + char pad6[2]; /* 179-180 */ + guint16 magic15; /* 181-182: 0x04, 0x00 */ + char pad10[2]; /* 183-184 */ + guint16 magic16; /* 185-186 */ + char pad11[2]; /* 187-188 */ + guint16 magic20; /* 189-190: 0x57, 0x04 */ + guint16 magic25; /* 191-192: 0x00, 0x04 */ + guint16 magic30; /* 193-194: 0x01, 0x00 */ + char pad20[2]; /* 195-196: 0x4a, 0x02 */ + guint16 magic35; /* 197-198: 0x4a, 0x02 */ + char pad30[10]; /* 199-208 */ + guint16 magic40; /* 209-210: 0x30, 0x00 */ + char pad40[2]; /* 211-212 */ + guint16 magic45; /* 213-214: 0x44, 0x00 */ + char pad45[2]; /* 215-216 */ + guint16 magic50; /* 217-218: 0x39, 0x00 */ + char pad50[2]; /* 219-220 */ + char data[256]; /* 221-EOP: a sequence of NULL-terminated strings: + - username; + - empty string (just a NULL); + - application name; + - hostname */ + } hello; + + struct { + char pad1[4]; /* 39-42 */ + guint16 magic2; /* 43-44 */ + char pad10[12]; /* 45-56 */ + guint32 bindaddr; /* 57-60: address to bind. */ + guint16 bindport; /* 61-62: port to bind. */ + char pad15[2]; /* 63-64 */ + guint16 magic3; /* 65-66 */ + guint16 boundport; /* 67-68 */ + char pad20[104]; /* 69-172 */ + char NTLMSSP[sizeof("NTLMSSP")]; /* 173-180: "NTLMSSP" */ + guint32 msgtype; /* 181-184: NTLM message type = 1 */ + guint32 flags; /* 185-188: NTLM message flags */ + guint16 magic20; /* 189-190: 0x28, 0x00 */ + char pad30[2]; /* 191-192 */ + guint16 magic25; /* 193-194: 0x96, 0x82 */ + guint16 magic30; /* 195-196: 0x01, 0x00 */ + char pad40[12]; /* 197-208 */ + guint16 magic50; /* 209-210: 0x30, 0x00 */ + char pad50[6]; /* 211-216 */ + guint16 magic55; /* 217-218: 0x30, 0x00 */ + char pad55[2]; /* 219-220 */ + char data[0]; /* Dummy end marker, no real data required */ + } auth; + + struct { + char pad1[4]; /* 39-42 */ + guint16 magic1; /* 43-44 */ + guint32 magic2; /* 45-48 */ + char pad2[8]; /* 49-56 */ + guint16 magic3; /* 57-58 */ + char pad3[6]; /* 59-64 */ + guint16 magic4; /* 65-66 */ + guint16 boundport; /* 67-68 */ + char pad4[104]; /* 69-172 */ + char NTLMSSP[sizeof("NTLMSSP")]; /* 173-180: "NTLMSSP" */ + guint32 msgtype; /* 181-184: NTLM message type = 3 */ + struct ntlm_buffer lm_resp; /* 185-192: LM response security buffer */ + struct ntlm_buffer ntlm_resp; /* 193-200: NTLM response security buffer */ + struct ntlm_buffer ntdomain_buf; /* 201-208: domain name security buffer */ + struct ntlm_buffer username_buf; /* 209-216: username security buffer */ + struct ntlm_buffer clienthost_buf; /* 217-224: hostname security buffer */ + struct ntlm_buffer sessionkey_buf; /* 225-232: session key security buffer */ + guint32 flags; /* 233-236: message flags */ + char data[1024]; /* 237-EOP: data area */ + } auth2; + + struct { + guint16 magic1; /* 39-40 */ + char pad1[2]; /* 41-42 */ + guint16 magic2; /* 43-44 */ + guint32 magic3; /* 45-48 */ + char pad5[8]; /* 48-56 */ + guint16 magic6; /* 57-58: 0x0200 */ + guint16 destport; /* 59-60 */ + guint32 destaddr; /* 61-64 */ + char pad10[4]; /* 65-68 */ + guint16 magic10; /* 69-70 */ + char pad15[2]; /* 71-72 */ + guint16 srcport; /* 73-74: port client connects from */ + char pad20[82]; /* 75-156 */ + char executable[256]; /* 76-EOP: application name */ + } connect; + + struct { + guint16 magic1; /* 39-40 */ + char pad5[2]; /* 41-42 */ + guint16 magic5; /* 43-44 */ + guint32 magic10; /* 45-48 */ + char pad10[2]; /* 49-50 */ + guint16 magic15; /* 51-52 */ + guint32 magic16; /* 53-56 */ + guint16 magic20; /* 57-58 */ + guint16 clientport; /* 59-60: forwarded port. */ + guint32 clientaddr; /* 61-64: forwarded address. */ + guint32 magic30; /* 65-68 */ + guint32 magic35; /* 69-72 */ + guint16 serverport; /* 73-74: port server will connect to us from. */ + guint16 srcport; /* 75-76: connect request; port used on client behalf. */ + guint16 boundport; /* 77-78: bind request; port used on client behalf. */ + guint32 boundaddr; /* 79-82: addr used on client behalf */ + char pad30[90]; /* 83-172 */ + char data[0]; /* End marker */ + } connack; + + } packet; +}; + +struct msproxy_response_t { + guint32 packetid; /* 1-4 */ + guint32 magic5; /* 5-8 */ + guint32 serverid; /* 9-12 */ + char clientack; /* 13: ack of last client packet. */ + char pad5[3]; /* 14-16 */ + unsigned char sequence; /* 17: sequence # of this packet. */ + char pad10[7]; /* 18-24 */ + char RWSP[4]; /* 25-28: 0x52,0x57,0x53,0x50 */ + char pad15[8]; /* 29-36 */ + guint16 command; /* 37-38 */ + + union { + struct { + char pad5[18]; /* 39-56 */ + guint16 magic20; /* 57-58: 0x02, 0x00 */ + char pad10[6]; /* 59-64 */ + guint16 magic30; /* 65-66: 0x74, 0x01 */ + char pad15[2]; /* 67-68 */ + guint16 magic35; /* 69-70: 0x0c, 0x00 */ + char pad20[6]; /* 71-76 */ + guint16 magic50; /* 77-78: 0x04, 0x00 */ + char pad30[6]; /* 79-84 */ + guint16 magic60; /* 85-86: 0x65, 0x05 */ + char pad35[2]; /* 87-88 */ + guint16 magic65; /* 89-90: 0x02, 0x00 */ + char pad40[8]; /* 91-98 */ + guint16 udpport; /* 99-100 */ + guint32 udpaddr; /* 101-104 */ + } hello; + + struct { + char pad1[6]; /* 39-44 */ + guint32 magic10; /* 45-48 */ + char pad3[10]; /* 49-58 */ + guint16 boundport; /* 59-60: port server bound for us. */ + guint32 boundaddr; /* 61-64: addr server bound for us. */ + char pad10[4]; /* 65-68 */ + guint16 magic15; /* 69-70 */ + char pad15[102]; /* 70-172 */ + char NTLMSSP[sizeof("NTLMSSP")]; /* 173-180: "NTLMSSP" */ + guint32 msgtype; /* 181-184: NTLM message type = 2 */ + struct ntlm_buffer target; /* 185-192: target security buffer */ + guint32 flags; /* 193-196: NTLM message flags */ + char challenge[8]; /* 197-204: NTLM challenge request */ + char context[8]; /* 205-212: NTLM context */ + char data[1024]; /* 213-EOP: target information data */ + } auth; + + struct { + guint16 magic1; /* 39-40 */ + char pad5[18]; /* 41-58 */ + guint16 clientport; /* 59-60: forwarded port. */ + guint32 clientaddr; /* 61-64: forwarded address. */ + guint32 magic10; /* 65-68 */ + guint32 magic15; /* 69-72 */ + guint16 serverport; /* 73-74: port server will connect to us from. */ + guint16 srcport; /* 75-76: connect request; port used on client behalf. */ + guint16 boundport; /* 77-78: bind request; port used on client behalf. */ + guint32 boundaddr; /* 79-82: addr used on client behalf */ + char pad10[90]; /* 83-172 */ + } connect; + } packet; +}; + +#pragma pack() + +int traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound); +void msproxy_keepalive (void); diff --git a/src/common/network.c b/src/common/network.c new file mode 100644 index 00000000..0c409506 --- /dev/null +++ b/src/common/network.c @@ -0,0 +1,383 @@ +/* X-Chat + * Copyright (C) 2001 Peter Zelezny. + * + * 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 + */ + +/* ipv4 and ipv6 networking functions with a common interface */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <glib.h> + +#include "../../config.h" /* grab USE_IPV6 and LOOKUPD defines */ + +#define WANTSOCKET +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#define NETWORK_PRIVATE +#include "network.h" + +#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n))) + + +/* ================== COMMON ================= */ + +static void +net_set_socket_options (int sok) +{ + socklen_t sw; + + sw = 1; + setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &sw, sizeof (sw)); + sw = 1; + setsockopt (sok, SOL_SOCKET, SO_KEEPALIVE, (char *) &sw, sizeof (sw)); +} + +char * +net_ip (guint32 addr) +{ + struct in_addr ia; + + ia.s_addr = htonl (addr); + return inet_ntoa (ia); +} + +void +net_store_destroy (netstore * ns) +{ +#ifdef USE_IPV6 + if (ns->ip6_hostent) + freeaddrinfo (ns->ip6_hostent); +#endif + free (ns); +} + +netstore * +net_store_new (void) +{ + netstore *ns; + + ns = malloc (sizeof (netstore)); + memset (ns, 0, sizeof (netstore)); + + return ns; +} + +#ifndef USE_IPV6 + +/* =================== IPV4 ================== */ + +/* + A note about net_resolve and lookupd: + + Many IRC networks rely on round-robin DNS for load balancing, rotating the list + of IP address on each query. However, this method breaks when DNS queries are + cached. Mac OS X and Darwin handle DNS lookups through the lookupd daemon, which + caches queries in its default configuration: thus, if we always pick the first + address, we will be stuck with the same host (which might be down!) until the + TTL reaches 0 or lookupd is reset (typically, at reboot). Therefore, we need to + pick a random address from the result list, instead of always using the first. +*/ + +char * +net_resolve (netstore * ns, char *hostname, int port, char **real_host) +{ + ns->ip4_hostent = gethostbyname (hostname); + if (!ns->ip4_hostent) + return NULL; + + memset (&ns->addr, 0, sizeof (ns->addr)); +#ifdef LOOKUPD + int count = 0; + while (ns->ip4_hostent->h_addr_list[count]) count++; + memcpy (&ns->addr.sin_addr, + ns->ip4_hostent->h_addr_list[RAND_INT(count)], + ns->ip4_hostent->h_length); +#else + memcpy (&ns->addr.sin_addr, ns->ip4_hostent->h_addr, + ns->ip4_hostent->h_length); +#endif + ns->addr.sin_port = htons (port); + ns->addr.sin_family = AF_INET; + + *real_host = strdup (ns->ip4_hostent->h_name); + return strdup (inet_ntoa (ns->addr.sin_addr)); +} + +int +net_connect (netstore * ns, int sok4, int sok6, int *sok_return) +{ + *sok_return = sok4; + return connect (sok4, (struct sockaddr *) &ns->addr, sizeof (ns->addr)); +} + +void +net_bind (netstore * tobindto, int sok4, int sok6) +{ + bind (sok4, (struct sockaddr *) &tobindto->addr, sizeof (tobindto->addr)); +} + +void +net_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_STREAM, 0); + *sok6 = -1; + net_set_socket_options (*sok4); +} + +void +udp_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_DGRAM, 0); + *sok6 = -1; +} + +void +net_store_fill_any (netstore *ns) +{ + ns->addr.sin_family = AF_INET; + ns->addr.sin_addr.s_addr = INADDR_ANY; + ns->addr.sin_port = 0; +} + +void +net_store_fill_v4 (netstore *ns, guint32 addr, int port) +{ + ns->addr.sin_family = AF_INET; + ns->addr.sin_addr.s_addr = addr; + ns->addr.sin_port = port; +} + +guint32 +net_getsockaddr_v4 (netstore *ns) +{ + return ns->addr.sin_addr.s_addr; +} + +int +net_getsockport (int sok4, int sok6) +{ + struct sockaddr_in addr; + int len = sizeof (addr); + + if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1) + return -1; + return addr.sin_port; +} + +#else + +/* =================== IPV6 ================== */ + +char * +net_resolve (netstore * ns, char *hostname, int port, char **real_host) +{ + struct addrinfo hints; + char ipstring[MAX_HOSTNAME]; + char portstring[MAX_HOSTNAME]; + int ret; + +/* if (ns->ip6_hostent) + freeaddrinfo (ns->ip6_hostent);*/ + + sprintf (portstring, "%d", port); + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = PF_UNSPEC; /* support ipv6 and ipv4 */ + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) + ret = getaddrinfo (hostname, NULL, &hints, &ns->ip6_hostent); + else + ret = getaddrinfo (hostname, portstring, &hints, &ns->ip6_hostent); + if (ret != 0) + return NULL; + +#ifdef LOOKUPD /* See note about lookupd above the IPv4 version of net_resolve. */ + struct addrinfo *tmp; + int count = 0; + + for (tmp = ns->ip6_hostent; tmp; tmp = tmp->ai_next) + count ++; + + count = RAND_INT(count); + + while (count--) ns->ip6_hostent = ns->ip6_hostent->ai_next; +#endif + + /* find the numeric IP number */ + ipstring[0] = 0; + getnameinfo (ns->ip6_hostent->ai_addr, ns->ip6_hostent->ai_addrlen, + ipstring, sizeof (ipstring), NULL, 0, NI_NUMERICHOST); + + if (ns->ip6_hostent->ai_canonname) + *real_host = strdup (ns->ip6_hostent->ai_canonname); + else + *real_host = strdup (hostname); + + return strdup (ipstring); +} + +/* the only thing making this interface unclean, this shitty sok4, sok6 business */ + +int +net_connect (netstore * ns, int sok4, int sok6, int *sok_return) +{ + struct addrinfo *res, *res0; + int error = -1; + + res0 = ns->ip6_hostent; + + for (res = res0; res; res = res->ai_next) + { +/* sok = socket (res->ai_family, res->ai_socktype, res->ai_protocol); + if (sok < 0) + continue;*/ + switch (res->ai_family) + { + case AF_INET: + error = connect (sok4, res->ai_addr, res->ai_addrlen); + *sok_return = sok4; + break; + case AF_INET6: + error = connect (sok6, res->ai_addr, res->ai_addrlen); + *sok_return = sok6; + break; + default: + error = 1; + } + + if (error == 0) + break; + } + + return error; +} + +void +net_bind (netstore * tobindto, int sok4, int sok6) +{ + bind (sok4, tobindto->ip6_hostent->ai_addr, + tobindto->ip6_hostent->ai_addrlen); + bind (sok6, tobindto->ip6_hostent->ai_addr, + tobindto->ip6_hostent->ai_addrlen); +} + +void +net_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + *sok6 = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP); + net_set_socket_options (*sok4); + net_set_socket_options (*sok6); +} + +void +udp_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + *sok6 = socket (AF_INET6, SOCK_DGRAM, IPPROTO_UDP); +} + +/* the following functions are used only by MSPROXY and are not + proper ipv6 implementations - do not use in new code! */ + +void +net_store_fill_any (netstore *ns) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + + ai = ns->ip6_hostent; + if (!ai) { + ai = malloc (sizeof (struct addrinfo)); + memset (ai, 0, sizeof (struct addrinfo)); + ns->ip6_hostent = ai; + } + sin = (struct sockaddr_in *)ai->ai_addr; + if (!sin) { + sin = malloc (sizeof (struct sockaddr_in)); + memset (sin, 0, sizeof (struct sockaddr_in)); + ai->ai_addr = (struct sockaddr *)sin; + } + ai->ai_family = AF_INET; + ai->ai_addrlen = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = INADDR_ANY; + sin->sin_port = 0; + ai->ai_next = NULL; +} + +void +net_store_fill_v4 (netstore *ns, guint32 addr, int port) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + + ai = ns->ip6_hostent; + if (!ai) { + ai = malloc (sizeof (struct addrinfo)); + memset (ai, 0, sizeof (struct addrinfo)); + ns->ip6_hostent = ai; + } + sin = (struct sockaddr_in *)ai->ai_addr; + if (!sin) { + sin = malloc (sizeof (struct sockaddr_in)); + memset (sin, 0, sizeof (struct sockaddr_in)); + ai->ai_addr = (struct sockaddr *)sin; + } + ai->ai_family = AF_INET; + ai->ai_addrlen = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = addr; + sin->sin_port = port; + ai->ai_next = NULL; +} + +guint32 +net_getsockaddr_v4 (netstore *ns) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + + ai = ns->ip6_hostent; + + while (ai->ai_family != AF_INET) { + ai = ai->ai_next; + if (!ai) + return 0; + } + sin = (struct sockaddr_in *)ai->ai_addr; + return sin->sin_addr.s_addr; +} + +int +net_getsockport (int sok4, int sok6) +{ + struct sockaddr_in addr; + int len = sizeof (addr); + + if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1) + return -1; + return addr.sin_port; +} + +#endif diff --git a/src/common/network.h b/src/common/network.h new file mode 100644 index 00000000..f45f210a --- /dev/null +++ b/src/common/network.h @@ -0,0 +1,34 @@ +#ifndef XCHAT_NETWORK_H +#define XCHAT_NETWORK_H + +typedef struct netstore_ +{ +#ifdef NETWORK_PRIVATE +#ifdef USE_IPV6 + struct addrinfo *ip6_hostent; +#else + struct hostent *ip4_hostent; + struct sockaddr_in addr; +#endif +#else + int _dummy; /* some compilers don't like empty structs */ +#endif +} netstore; + +#define MAX_HOSTNAME 128 + +netstore *net_store_new (void); +void net_store_destroy (netstore *ns); +int net_connect (netstore *ns, int sok4, int sok6, int *sok_return); +char *net_resolve (netstore *ns, char *hostname, int port, char **real_host); +void net_bind (netstore *tobindto, int sok4, int sok6); +char *net_ip (guint32 addr); +void net_sockets (int *sok4, int *sok6); +/* functions for MSPROXY only! */ +void udp_sockets (int *sok4, int *sok6); +void net_store_fill_any (netstore *ns); +void net_store_fill_v4 (netstore *ns, guint32 addr, int port); +guint32 net_getsockaddr_v4 (netstore *ns); +int net_getsockport(int sok4, int sok6); + +#endif diff --git a/src/common/notify.c b/src/common/notify.c new file mode 100644 index 00000000..04795849 --- /dev/null +++ b/src/common/notify.c @@ -0,0 +1,634 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> + +#include "xchat.h" +#include "notify.h" +#include "cfgfiles.h" +#include "fe.h" +#include "server.h" +#include "text.h" +#include "util.h" +#include "xchatc.h" + + +GSList *notify_list = 0; +int notify_tag = 0; + + +static char * +despacify_dup (char *str) +{ + char *p, *res = malloc (strlen (str) + 1); + + p = res; + while (1) + { + if (*str != ' ') + { + *p = *str; + if (*p == 0) + return res; + p++; + } + str++; + } +} + +static int +notify_netcmp (char *str, void *serv) +{ + char *net = despacify_dup (server_get_network (serv, TRUE)); + + if (rfc_casecmp (str, net) == 0) + { + free (net); + return 0; /* finish & return FALSE from token_foreach() */ + } + + free (net); + return 1; /* keep going... */ +} + +/* monitor this nick on this particular network? */ + +static gboolean +notify_do_network (struct notify *notify, server *serv) +{ + if (!notify->networks) /* ALL networks for this nick */ + return TRUE; + + if (token_foreach (notify->networks, ',', notify_netcmp, serv)) + return FALSE; /* network list doesn't contain this one */ + + return TRUE; +} + +struct notify_per_server * +notify_find_server_entry (struct notify *notify, struct server *serv) +{ + GSList *list = notify->server_list; + struct notify_per_server *servnot; + + while (list) + { + servnot = (struct notify_per_server *) list->data; + if (servnot->server == serv) + return servnot; + list = list->next; + } + + /* not found, should we add it, or is this not a network where + we're monitoring this nick? */ + if (!notify_do_network (notify, serv)) + return NULL; + + servnot = malloc (sizeof (struct notify_per_server)); + if (servnot) + { + memset (servnot, 0, sizeof (struct notify_per_server)); + servnot->server = serv; + servnot->notify = notify; + notify->server_list = g_slist_prepend (notify->server_list, servnot); + } + return servnot; +} + +void +notify_save (void) +{ + int fh; + struct notify *notify; + GSList *list = notify_list; + + fh = xchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + while (list) + { + notify = (struct notify *) list->data; + write (fh, notify->name, strlen (notify->name)); + if (notify->networks) + { + write (fh, " ", 1); + write (fh, notify->networks, strlen (notify->networks)); + } + write (fh, "\n", 1); + list = list->next; + } + close (fh); + } +} + +void +notify_load (void) +{ + int fh; + char buf[256]; + char *sep; + + fh = xchat_open_file ("notify.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + while (waitline (fh, buf, sizeof buf, FALSE) != -1) + { + if (buf[0] != '#' && buf[0] != 0) + { + sep = strchr (buf, ' '); + if (sep) + { + sep[0] = 0; + notify_adduser (buf, sep + 1); + } + else + notify_adduser (buf, NULL); + } + } + close (fh); + } +} + +static struct notify_per_server * +notify_find (server *serv, char *nick) +{ + GSList *list = notify_list; + struct notify_per_server *servnot; + struct notify *notify; + + while (list) + { + notify = (struct notify *) list->data; + + servnot = notify_find_server_entry (notify, serv); + if (!servnot) + { + list = list->next; + continue; + } + + if (!serv->p_cmp (notify->name, nick)) + return servnot; + + list = list->next; + } + + return 0; +} + +static void +notify_announce_offline (server * serv, struct notify_per_server *servnot, + char *nick, int quiet) +{ + session *sess; + + sess = serv->front_session; + + servnot->ison = FALSE; + servnot->lastoff = time (0); + if (!quiet) + EMIT_SIGNAL (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername, + server_get_network (serv, TRUE), NULL, 0); + fe_notify_update (nick); + fe_notify_update (0); +} + +static void +notify_announce_online (server * serv, struct notify_per_server *servnot, + char *nick) +{ + session *sess; + + sess = serv->front_session; + + servnot->lastseen = time (0); + if (servnot->ison) + return; + + servnot->ison = TRUE; + servnot->laston = time (0); + EMIT_SIGNAL (XP_TE_NOTIFYONLINE, sess, nick, serv->servername, + server_get_network (serv, TRUE), NULL, 0); + fe_notify_update (nick); + fe_notify_update (0); + + if (prefs.whois_on_notifyonline) + { + + /* Let's do whois with idle time (like in /quote WHOIS %s %s) */ + + char *wii_str = malloc (strlen (nick) * 2 + 2); + sprintf (wii_str, "%s %s", nick, nick); + serv->p_whois (serv, wii_str); + free (wii_str); + } +} + +/* handles numeric 601 */ + +void +notify_set_offline (server * serv, char *nick, int quiet) +{ + struct notify_per_server *servnot; + + servnot = notify_find (serv, nick); + if (!servnot) + return; + + notify_announce_offline (serv, servnot, nick, quiet); +} + +/* handles numeric 604 and 600 */ + +void +notify_set_online (server * serv, char *nick) +{ + struct notify_per_server *servnot; + + servnot = notify_find (serv, nick); + if (!servnot) + return; + + notify_announce_online (serv, servnot, nick); +} + +static void +notify_watch (server * serv, char *nick, int add) +{ + char tbuf[256]; + + snprintf (tbuf, sizeof (tbuf), "WATCH +%s", nick); + if (!add) + tbuf[6] = '-'; + serv->p_raw (serv, tbuf); +} + +static void +notify_watch_all (struct notify *notify, int add) +{ + server *serv; + GSList *list = serv_list; + while (list) + { + serv = list->data; + if (serv->connected && serv->end_of_motd && serv->supports_watch && + notify_do_network (notify, serv)) + notify_watch (serv, notify->name, add); + list = list->next; + } +} + +static void +notify_flush_watches (server * serv, GSList *from, GSList *end) +{ + char tbuf[512]; + GSList *list; + struct notify *notify; + + strcpy (tbuf, "WATCH"); + + list = from; + while (list != end) + { + notify = list->data; + strcat (tbuf, " +"); + strcat (tbuf, notify->name); + list = list->next; + } + serv->p_raw (serv, tbuf); +} + +/* called when logging in. e.g. when End of motd. */ + +void +notify_send_watches (server * serv) +{ + struct notify *notify; + GSList *list; + GSList *point; + int len; + + len = 0; + point = list = notify_list; + while (list) + { + notify = list->data; + + if (notify_do_network (notify, serv)) + { + len += strlen (notify->name) + 2 /* + and space */; + if (len > 500) + { + notify_flush_watches (serv, point, list); + len = strlen (notify->name) + 2; + point = list; + } + } + + list = list->next; + } + + if (point) + notify_flush_watches (serv, point, NULL); +} + +/* called when receiving a ISON 303 - should this func go? */ + +void +notify_markonline (server *serv, char *word[]) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + int i, seen; + + while (list) + { + notify = (struct notify *) list->data; + servnot = notify_find_server_entry (notify, serv); + if (!servnot) + { + list = list->next; + continue; + } + i = 4; + seen = FALSE; + while (*word[i]) + { + if (!serv->p_cmp (notify->name, word[i])) + { + seen = TRUE; + notify_announce_online (serv, servnot, notify->name); + break; + } + i++; + /* FIXME: word[] is only a 32 element array, limits notify list to + about 27 people */ + if (i > PDIWORDS - 5) + { + /*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/ + break; + } + } + if (!seen && servnot->ison) + { + notify_announce_offline (serv, servnot, notify->name, FALSE); + } + list = list->next; + } + fe_notify_update (0); +} + +/* yuck! Old routine for ISON notify */ + +static void +notify_checklist_for_server (server *serv) +{ + char outbuf[512]; + struct notify *notify; + GSList *list = notify_list; + int i = 0; + + strcpy (outbuf, "ISON "); + while (list) + { + notify = list->data; + if (notify_do_network (notify, serv)) + { + i++; + strcat (outbuf, notify->name); + strcat (outbuf, " "); + if (strlen (outbuf) > 460) + { + /* LAME: we can't send more than 512 bytes to the server, but * + * if we split it in two packets, our offline detection wouldn't * + work */ + /*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/ + break; + } + } + list = list->next; + } + + if (i) + serv->p_raw (serv, outbuf); +} + +int +notify_checklist (void) /* check ISON list */ +{ + struct server *serv; + GSList *list = serv_list; + + while (list) + { + serv = list->data; + if (serv->connected && serv->end_of_motd && !serv->supports_watch) + { + notify_checklist_for_server (serv); + } + list = list->next; + } + return 1; +} + +void +notify_showlist (struct session *sess) +{ + char outbuf[256]; + struct notify *notify; + GSList *list = notify_list; + struct notify_per_server *servnot; + int i = 0; + + EMIT_SIGNAL (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0); + while (list) + { + i++; + notify = (struct notify *) list->data; + servnot = notify_find_server_entry (notify, sess->server); + if (servnot && servnot->ison) + snprintf (outbuf, sizeof (outbuf), _(" %-20s online\n"), notify->name); + else + snprintf (outbuf, sizeof (outbuf), _(" %-20s offline\n"), notify->name); + PrintText (sess, outbuf); + list = list->next; + } + if (i) + { + sprintf (outbuf, "%d", i); + EMIT_SIGNAL (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL, 0); + } else + EMIT_SIGNAL (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0); +} + +int +notify_deluser (char *name) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + + while (list) + { + notify = (struct notify *) list->data; + if (!rfc_casecmp (notify->name, name)) + { + fe_notify_update (notify->name); + /* Remove the records for each server */ + while (notify->server_list) + { + servnot = (struct notify_per_server *) notify->server_list->data; + notify->server_list = + g_slist_remove (notify->server_list, servnot); + free (servnot); + } + notify_list = g_slist_remove (notify_list, notify); + notify_watch_all (notify, FALSE); + if (notify->networks) + free (notify->networks); + free (notify->name); + free (notify); + fe_notify_update (0); + return 1; + } + list = list->next; + } + return 0; +} + +void +notify_adduser (char *name, char *networks) +{ + struct notify *notify = malloc (sizeof (struct notify)); + if (notify) + { + memset (notify, 0, sizeof (struct notify)); + if (strlen (name) >= NICKLEN) + { + notify->name = malloc (NICKLEN); + safe_strcpy (notify->name, name, NICKLEN); + } else + { + notify->name = strdup (name); + } + if (networks) + notify->networks = despacify_dup (networks); + notify->server_list = 0; + notify_list = g_slist_prepend (notify_list, notify); + notify_checklist (); + fe_notify_update (notify->name); + fe_notify_update (0); + notify_watch_all (notify, TRUE); + } +} + +gboolean +notify_is_in_list (server *serv, char *name) +{ + struct notify *notify; + GSList *list = notify_list; + + while (list) + { + notify = (struct notify *) list->data; + if (!serv->p_cmp (notify->name, name)) + return TRUE; + list = list->next; + } + + return FALSE; +} + +int +notify_isnotify (struct session *sess, char *name) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + + while (list) + { + notify = (struct notify *) list->data; + if (!sess->server->p_cmp (notify->name, name)) + { + servnot = notify_find_server_entry (notify, sess->server); + if (servnot && servnot->ison) + return TRUE; + } + list = list->next; + } + + return FALSE; +} + +void +notify_cleanup () +{ + GSList *list = notify_list; + GSList *nslist, *srvlist; + struct notify *notify; + struct notify_per_server *servnot; + struct server *serv; + int valid; + + while (list) + { + /* Traverse the list of notify structures */ + notify = (struct notify *) list->data; + nslist = notify->server_list; + while (nslist) + { + /* Look at each per-server structure */ + servnot = (struct notify_per_server *) nslist->data; + + /* Check the server is valid */ + valid = FALSE; + srvlist = serv_list; + while (srvlist) + { + serv = (struct server *) srvlist->data; + if (servnot->server == serv) + { + valid = serv->connected; /* Only valid if server is too */ + break; + } + srvlist = srvlist->next; + } + if (!valid) + { + notify->server_list = + g_slist_remove (notify->server_list, servnot); + free (servnot); + nslist = notify->server_list; + } else + { + nslist = nslist->next; + } + } + list = list->next; + } + fe_notify_update (0); +} diff --git a/src/common/notify.h b/src/common/notify.h new file mode 100644 index 00000000..37674fe5 --- /dev/null +++ b/src/common/notify.h @@ -0,0 +1,44 @@ +#ifndef XCHAT_NOTIFY_H +#define XCHAT_NOTIFY_H + +struct notify +{ + char *name; + char *networks; /* network names, comma sep */ + GSList *server_list; +}; + +struct notify_per_server +{ + struct server *server; + struct notify *notify; + time_t laston; + time_t lastseen; + time_t lastoff; + unsigned int ison:1; +}; + +extern GSList *notify_list; +extern int notify_tag; + +/* the WATCH stuff */ +void notify_set_online (server * serv, char *nick); +void notify_set_offline (server * serv, char *nick, int quiet); +void notify_send_watches (server * serv); + +/* the general stuff */ +void notify_adduser (char *name, char *networks); +int notify_deluser (char *name); +void notify_cleanup (void); +void notify_load (void); +void notify_save (void); +void notify_showlist (session *sess); +gboolean notify_is_in_list (server *serv, char *name); +int notify_isnotify (session *sess, char *name); +struct notify_per_server *notify_find_server_entry (struct notify *notify, struct server *serv); + +/* the old ISON stuff - remove me? */ +void notify_markonline (server *serv, char *word[]); +int notify_checklist (void); + +#endif diff --git a/src/common/outbound.c b/src/common/outbound.c new file mode 100644 index 00000000..7c6e5e6a --- /dev/null +++ b/src/common/outbound.c @@ -0,0 +1,4403 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 + */ + +#define _GNU_SOURCE /* for memrchr */ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <limits.h> +#include <errno.h> + +#define WANTSOCKET +#define WANTARPA +#include "inet.h" + +#ifndef WIN32 +#include <sys/wait.h> +#endif + +#include <unistd.h> +#include <time.h> +#include <signal.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "xchat.h" +#include "plugin.h" +#include "ignore.h" +#include "util.h" +#include "fe.h" +#include "cfgfiles.h" /* xchat_fopen_file() */ +#include "network.h" /* net_ip() */ +#include "modes.h" +#include "notify.h" +#include "inbound.h" +#include "text.h" +#include "xchatc.h" +#include "servlist.h" +#include "server.h" +#include "tree.h" +#include "outbound.h" + + +#ifdef USE_DEBUG +extern int current_mem_usage; +#endif +#define TBUFSIZE 4096 + +static void help (session *sess, char *tbuf, char *helpcmd, int quiet); +static int cmd_server (session *sess, char *tbuf, char *word[], char *word_eol[]); +static void handle_say (session *sess, char *text, int check_spch); + + +static void +notj_msg (struct session *sess) +{ + PrintText (sess, _("No channel joined. Try /join #<channel>\n")); +} + +void +notc_msg (struct session *sess) +{ + PrintText (sess, _("Not connected. Try /server <host> [<port>]\n")); +} + +static char * +random_line (char *file_name) +{ + FILE *fh; + char buf[512]; + int lines, ran; + + if (!file_name[0]) + goto nofile; + + fh = xchat_fopen_file (file_name, "r", 0); + if (!fh) + { + nofile: + /* reason is not a file, an actual reason! */ + return strdup (file_name); + } + + /* count number of lines in file */ + lines = 0; + while (fgets (buf, sizeof (buf), fh)) + lines++; + + if (lines < 1) + goto nofile; + + /* go down a random number */ + rewind (fh); + ran = RAND_INT (lines); + do + { + fgets (buf, sizeof (buf), fh); + lines--; + } + while (lines > ran); + fclose (fh); + buf[strlen (buf) - 1] = 0; /* remove the trailing '\n' */ + return strdup (buf); +} + +void +server_sendpart (server * serv, char *channel, char *reason) +{ + if (!reason) + { + reason = random_line (prefs.partreason); + serv->p_part (serv, channel, reason); + free (reason); + } else + { + /* reason set by /quit, /close argument */ + serv->p_part (serv, channel, reason); + } +} + +void +server_sendquit (session * sess) +{ + char *rea, *colrea; + + if (!sess->quitreason) + { + colrea = strdup (prefs.quitreason); + check_special_chars (colrea, FALSE); + rea = random_line (colrea); + free (colrea); + sess->server->p_quit (sess->server, rea); + free (rea); + } else + { + /* reason set by /quit, /close argument */ + sess->server->p_quit (sess->server, sess->quitreason); + } +} + +void +process_data_init (char *buf, char *cmd, char *word[], + char *word_eol[], gboolean handle_quotes, + gboolean allow_escape_quotes) +{ + int wordcount = 2; + int space = FALSE; + int quote = FALSE; + int j = 0; + int len; + + word[0] = "\000\000"; + word_eol[0] = "\000\000"; + word[1] = (char *)buf; + word_eol[1] = (char *)cmd; + + while (1) + { + switch (*cmd) + { + case 0: + buf[j] = 0; + for (j = wordcount; j < PDIWORDS; j++) + { + word[j] = "\000\000"; + word_eol[j] = "\000\000"; + } + return; + case '\042': + if (!handle_quotes) + goto def; + /* two quotes turn into 1 */ + if (allow_escape_quotes && cmd[1] == '\042') + { + cmd++; + goto def; + } + if (quote) + { + quote = FALSE; + space = FALSE; + } else + quote = TRUE; + cmd++; + break; + case ' ': + if (!quote) + { + if (!space) + { + buf[j] = 0; + j++; + + if (wordcount < PDIWORDS) + { + word[wordcount] = &buf[j]; + word_eol[wordcount] = cmd + 1; + wordcount++; + } + + space = TRUE; + } + cmd++; + break; + } + default: +def: + space = FALSE; + len = g_utf8_skip[((unsigned char *)cmd)[0]]; + if (len == 1) + { + buf[j] = *cmd; + j++; + cmd++; + } else + { + /* skip past a multi-byte utf8 char */ + memcpy (buf + j, cmd, len); + j += len; + cmd += len; + } + } + } +} + +static int +cmd_addbutton (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + if (*word[2] && *word_eol[3]) + { + if (sess->type == SESS_DIALOG) + { + list_addentry (&dlgbutton_list, word_eol[3], word[2]); + fe_dlgbuttons_update (sess); + } else + { + list_addentry (&button_list, word_eol[3], word[2]); + fe_buttons_update (sess); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_allchannels (session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list = sess_list; + + if (!*word_eol[2]) + return FALSE; + + while (list) + { + sess = list->data; + if (sess->type == SESS_CHANNEL && sess->channel[0] && sess->server->connected) + { + handle_command (sess, word_eol[2], FALSE); + } + list = list->next; + } + + return TRUE; +} + +static int +cmd_allchannelslocal (session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list = sess_list; + server *serv = sess->server; + + if (!*word_eol[2]) + return FALSE; + + while (list) + { + sess = list->data; + if (sess->type == SESS_CHANNEL && sess->channel[0] && + sess->server->connected && sess->server == serv) + { + handle_command (sess, word_eol[2], FALSE); + } + list = list->next; + } + + return TRUE; +} + +static int +cmd_allservers (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + GSList *list; + server *serv; + + if (!*word_eol[2]) + return FALSE; + + list = serv_list; + while (list) + { + serv = list->data; + if (serv->connected) + handle_command (serv->front_session, word_eol[2], FALSE); + list = list->next; + } + + return TRUE; +} + +static int +cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list; + char *reason = word_eol[2]; + + if (!(*reason)) + { + if (sess->server->is_away) + { + if (sess->server->last_away_reason) + PrintTextf (sess, _("Already marked away: %s\n"), sess->server->last_away_reason); + return FALSE; + } + + if (sess->server->reconnect_away) + reason = sess->server->last_away_reason; + else + /* must manage memory pointed to by random_line() */ + reason = random_line (prefs.awayreason); + } + sess->server->p_set_away (sess->server, reason); + + if (prefs.show_away_message) + { + snprintf (tbuf, TBUFSIZE, "me is away: %s", reason); + for (list = sess_list; list; list = list->next) + { + /* am I the right server and not a dialog box */ + if (((struct session *) list->data)->server == sess->server + && ((struct session *) list->data)->type == SESS_CHANNEL + && ((struct session *) list->data)->channel[0]) + { + handle_command ((session *) list->data, tbuf, TRUE); + } + } + } + + if (sess->server->last_away_reason != reason) + { + if (sess->server->last_away_reason) + free (sess->server->last_away_reason); + + if (reason == word_eol[2]) + sess->server->last_away_reason = strdup (reason); + else + sess->server->last_away_reason = reason; + } + + if (!sess->server->connected) + sess->server->reconnect_away = 1; + + return TRUE; +} + +static int +cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list; + unsigned int gone; + + if (sess->server->is_away) + { + sess->server->p_set_back (sess->server); + + if (prefs.show_away_message) + { + gone = time (NULL) - sess->server->away_time; + sprintf (tbuf, "me is back (gone %.2d:%.2d:%.2d)", gone / 3600, + (gone / 60) % 60, gone % 60); + for (list = sess_list; list; list = list->next) + { + /* am I the right server and not a dialog box */ + if (((struct session *) list->data)->server == sess->server + && ((struct session *) list->data)->type == SESS_CHANNEL + && ((struct session *) list->data)->channel[0]) + { + handle_command ((session *) list->data, tbuf, TRUE); + } + } + } + } + else + { + PrintText (sess, _("Already marked back.\n")); + } + + if (sess->server->last_away_reason) + free (sess->server->last_away_reason); + sess->server->last_away_reason = NULL; + + return TRUE; +} + +static void +ban (session * sess, char *tbuf, char *mask, char *bantypestr, int deop) +{ + int bantype; + struct User *user; + char *at, *dot, *lastdot; + char username[64], fullhost[128], domain[128], *mode, *p2; + server *serv = sess->server; + + user = userlist_find (sess, mask); + if (user && user->hostname) /* it's a nickname, let's find a proper ban mask */ + { + if (deop) + { + mode = "-o+b "; + p2 = user->nick; + } else + { + mode = "+b"; + p2 = ""; + } + + mask = user->hostname; + + at = strchr (mask, '@'); /* FIXME: utf8 */ + if (!at) + return; /* can't happen? */ + *at = 0; + + if (mask[0] == '~' || mask[0] == '+' || + mask[0] == '=' || mask[0] == '^' || mask[0] == '-') + { + /* the ident is prefixed with something, we replace that sign with an * */ + safe_strcpy (username+1, mask+1, sizeof (username)-1); + username[0] = '*'; + } else if (at - mask < USERNAMELEN) + { + /* we just add an * in the begining of the ident */ + safe_strcpy (username+1, mask, sizeof (username)-1); + username[0] = '*'; + } else + { + /* ident might be too long, we just ban what it gives and add an * in the end */ + safe_strcpy (username, mask, sizeof (username)); + } + *at = '@'; + safe_strcpy (fullhost, at + 1, sizeof (fullhost)); + + dot = strchr (fullhost, '.'); + if (dot) + { + safe_strcpy (domain, dot, sizeof (domain)); + } else + { + safe_strcpy (domain, fullhost, sizeof (domain)); + } + + if (*bantypestr) + bantype = atoi (bantypestr); + else + bantype = prefs.bantype; + + tbuf[0] = 0; + if (inet_addr (fullhost) != -1) /* "fullhost" is really a IP number */ + { + lastdot = strrchr (fullhost, '.'); + if (!lastdot) + return; /* can't happen? */ + + *lastdot = 0; + strcpy (domain, fullhost); + *lastdot = '.'; + + switch (bantype) + { + case 0: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s.*", mode, p2, domain); + break; + + case 1: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost); + break; + + case 2: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s.*", mode, p2, username, domain); + break; + + case 3: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost); + break; + } + } else + { + switch (bantype) + { + case 0: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@*%s", mode, p2, domain); + break; + + case 1: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost); + break; + + case 2: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@*%s", mode, p2, username, domain); + break; + + case 3: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost); + break; + } + } + + } else + { + snprintf (tbuf, TBUFSIZE, "+b %s", mask); + } + serv->p_mode (serv, sess->channel, tbuf); +} + +static int +cmd_ban (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *mask = word[2]; + + if (*mask) + { + ban (sess, tbuf, mask, word[3], 0); + } else + { + sess->server->p_mode (sess->server, sess->channel, "+b"); /* banlist */ + } + + return TRUE; +} + +static int +cmd_unban (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + /* Allow more than one mask in /unban -- tvk */ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'b', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_chanopt (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + /* chanopt.c */ + return chanopt_command (sess, tbuf, word, word_eol); +} + +static int +cmd_charset (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + server *serv = sess->server; + const char *locale = NULL; + int offset = 0; + + if (strcmp (word[2], "-quiet") == 0) + offset++; + + if (!word[2 + offset][0]) + { + g_get_charset (&locale); + PrintTextf (sess, "Current charset: %s\n", + serv->encoding ? serv->encoding : locale); + return TRUE; + } + + if (servlist_check_encoding (word[2 + offset])) + { + server_set_encoding (serv, word[2 + offset]); + if (offset < 1) + PrintTextf (sess, "Charset changed to: %s\n", word[2 + offset]); + } else + { + PrintTextf (sess, "\0034Unknown charset:\017 %s\n", word[2 + offset]); + } + + return TRUE; +} + +static int +cmd_clear (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list = sess_list; + char *reason = word_eol[2]; + + if (strcasecmp (reason, "HISTORY") == 0) + { + history_free (&sess->history); + return TRUE; + } + + if (strncasecmp (reason, "all", 3) == 0) + { + while (list) + { + sess = list->data; + if (!sess->nick_said) + fe_text_clear (list->data, 0); + list = list->next; + } + return TRUE; + } + + if (reason[0] != '-' && !isdigit (reason[0]) && reason[0] != 0) + return FALSE; + + fe_text_clear (sess, atoi (reason)); + return TRUE; +} + +static int +cmd_close (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list; + + if (strcmp (word[2], "-m") == 0) + { + list = sess_list; + while (list) + { + sess = list->data; + list = list->next; + if (sess->type == SESS_DIALOG) + fe_close_window (sess); + } + } else + { + if (*word_eol[2]) + sess->quitreason = word_eol[2]; + fe_close_window (sess); + } + + return TRUE; +} + +static int +cmd_ctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int mbl; + char *to = word[2]; + if (*to) + { + char *msg = word_eol[3]; + if (*msg) + { + unsigned char *cmd = (unsigned char *)msg; + + /* make the first word upper case (as per RFC) */ + while (1) + { + if (*cmd == ' ' || *cmd == 0) + break; + mbl = g_utf8_skip[*cmd]; + if (mbl == 1) + *cmd = toupper (*cmd); + cmd += mbl; + } + + sess->server->p_ctcp (sess->server, to, msg); + + EMIT_SIGNAL (XP_TE_CTCPSEND, sess, to, msg, NULL, NULL, 0); + + return TRUE; + } + } + return FALSE; +} + +static int +cmd_country (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *code = word[2]; + if (*code) + { + /* search? */ + if (strcmp (code, "-s") == 0) + { + country_search (word[3], sess, (void *)PrintTextf); + return TRUE; + } + + /* search, but forgot the -s */ + if (strchr (code, '*')) + { + country_search (code, sess, (void *)PrintTextf); + return TRUE; + } + + sprintf (tbuf, "%s = %s\n", code, country (code)); + PrintText (sess, tbuf); + return TRUE; + } + return FALSE; +} + +static int +cmd_cycle (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *key = sess->channelkey; + char *chan = word[2]; + if (!*chan) + chan = sess->channel; + if (*chan && sess->type == SESS_CHANNEL) + { + sess->server->p_cycle (sess->server, chan, key); + return TRUE; + } + return FALSE; +} + +static int +cmd_dcc (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int goodtype; + struct DCC *dcc = 0; + char *type = word[2]; + if (*type) + { + if (!strcasecmp (type, "HELP")) + return FALSE; + if (!strcasecmp (type, "CLOSE")) + { + if (*word[3] && *word[4]) + { + goodtype = 0; + if (!strcasecmp (word[3], "SEND")) + { + dcc = find_dcc (word[4], word[5], TYPE_SEND); + dcc_abort (sess, dcc); + goodtype = TRUE; + } + if (!strcasecmp (word[3], "GET")) + { + dcc = find_dcc (word[4], word[5], TYPE_RECV); + dcc_abort (sess, dcc); + goodtype = TRUE; + } + if (!strcasecmp (word[3], "CHAT")) + { + dcc = find_dcc (word[4], "", TYPE_CHATRECV); + if (!dcc) + dcc = find_dcc (word[4], "", TYPE_CHATSEND); + dcc_abort (sess, dcc); + goodtype = TRUE; + } + + if (!goodtype) + return FALSE; + + if (!dcc) + EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0); + + return TRUE; + + } + return FALSE; + } + if ((!strcasecmp (type, "CHAT")) || (!strcasecmp (type, "PCHAT"))) + { + char *nick = word[3]; + int passive = (!strcasecmp(type, "PCHAT")) ? 1 : 0; + if (*nick) + dcc_chat (sess, nick, passive); + return TRUE; + } + if (!strcasecmp (type, "LIST")) + { + dcc_show_list (sess); + return TRUE; + } + if (!strcasecmp (type, "GET")) + { + char *nick = word[3]; + char *file = word[4]; + if (!*file) + { + if (*nick) + dcc_get_nick (sess, nick); + } else + { + dcc = find_dcc (nick, file, TYPE_RECV); + if (dcc) + dcc_get (dcc); + else + EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0); + } + return TRUE; + } + if ((!strcasecmp (type, "SEND")) || (!strcasecmp (type, "PSEND"))) + { + int i = 3, maxcps; + char *nick, *file; + int passive = (!strcasecmp(type, "PSEND")) ? 1 : 0; + + nick = word[i]; + if (!*nick) + return FALSE; + + maxcps = prefs.dcc_max_send_cps; + if (!strncasecmp(nick, "-maxcps=", 8)) + { + maxcps = atoi(nick + 8); + i++; + nick = word[i]; + if (!*nick) + return FALSE; + } + + i++; + + file = word[i]; + if (!*file) + { + fe_dcc_send_filereq (sess, nick, maxcps, passive); + return TRUE; + } + + do + { + dcc_send (sess, nick, file, maxcps, passive); + i++; + file = word[i]; + } + while (*file); + + return TRUE; + } + + return FALSE; + } + + dcc_show_list (sess); + return TRUE; +} + +static int +cmd_debug (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + struct session *s; + struct server *v; + GSList *list = sess_list; + + PrintText (sess, "Session T Channel WaitChan WillChan Server\n"); + while (list) + { + s = (struct session *) list->data; + sprintf (tbuf, "%p %1x %-10.10s %-10.10s %-10.10s %p\n", + s, s->type, s->channel, s->waitchannel, + s->willjoinchannel, s->server); + PrintText (sess, tbuf); + list = list->next; + } + + list = serv_list; + PrintText (sess, "Server Sock Name\n"); + while (list) + { + v = (struct server *) list->data; + sprintf (tbuf, "%p %-5d %s\n", + v, v->sok, v->servername); + PrintText (sess, tbuf); + list = list->next; + } + + sprintf (tbuf, + "\nfront_session: %p\n" + "current_tab: %p\n\n", + sess->server->front_session, current_tab); + PrintText (sess, tbuf); +#ifdef USE_DEBUG + sprintf (tbuf, "current mem: %d\n\n", current_mem_usage); + PrintText (sess, tbuf); +#endif /* !MEMORY_DEBUG */ + + return TRUE; +} + +static int +cmd_delbutton (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + if (*word[2]) + { + if (sess->type == SESS_DIALOG) + { + if (list_delentry (&dlgbutton_list, word[2])) + fe_dlgbuttons_update (sess); + } else + { + if (list_delentry (&button_list, word[2])) + fe_buttons_update (sess); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_dehop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'h', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_deop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'o', 0); + return TRUE; + } + i++; + } +} + +typedef struct +{ + char **nicks; + int i; + session *sess; + char *reason; + char *tbuf; +} multidata; + +static int +mdehop_cb (struct User *user, multidata *data) +{ + if (user->hop && !user->me) + { + data->nicks[data->i] = user->nick; + data->i++; + } + return TRUE; +} + +static int +cmd_mdehop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char **nicks = malloc (sizeof (char *) * sess->hops); + multidata data; + + data.nicks = nicks; + data.i = 0; + tree_foreach (sess->usertree, (tree_traverse_func *)mdehop_cb, &data); + send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'h', 0); + free (nicks); + + return TRUE; +} + +static int +mdeop_cb (struct User *user, multidata *data) +{ + if (user->op && !user->me) + { + data->nicks[data->i] = user->nick; + data->i++; + } + return TRUE; +} + +static int +cmd_mdeop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char **nicks = malloc (sizeof (char *) * sess->ops); + multidata data; + + data.nicks = nicks; + data.i = 0; + tree_foreach (sess->usertree, (tree_traverse_func *)mdeop_cb, &data); + send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'o', 0); + free (nicks); + + return TRUE; +} + +GSList *menu_list = NULL; + +static void +menu_free (menu_entry *me) +{ + free (me->path); + if (me->label) + free (me->label); + if (me->cmd) + free (me->cmd); + if (me->ucmd) + free (me->ucmd); + if (me->group) + free (me->group); + if (me->icon) + free (me->icon); + free (me); +} + +/* strings equal? but ignore underscores */ + +int +menu_streq (const char *s1, const char *s2, int def) +{ + /* for separators */ + if (s1 == NULL && s2 == NULL) + return 0; + if (s1 == NULL || s2 == NULL) + return 1; + while (*s1) + { + if (*s1 == '_') + s1++; + if (*s2 == '_') + s2++; + if (*s1 != *s2) + return 1; + s1++; + s2++; + } + if (!*s2) + return 0; + return def; +} + +static menu_entry * +menu_entry_find (char *path, char *label) +{ + GSList *list; + menu_entry *me; + + list = menu_list; + while (list) + { + me = list->data; + if (!strcmp (path, me->path)) + { + if (me->label && label && !strcmp (label, me->label)) + return me; + } + list = list->next; + } + return NULL; +} + +static void +menu_del_children (char *path, char *label) +{ + GSList *list, *next; + menu_entry *me; + char buf[512]; + + if (!label) + label = ""; + if (path[0]) + snprintf (buf, sizeof (buf), "%s/%s", path, label); + else + snprintf (buf, sizeof (buf), "%s", label); + + list = menu_list; + while (list) + { + me = list->data; + next = list->next; + if (!menu_streq (buf, me->path, 0)) + { + menu_list = g_slist_remove (menu_list, me); + menu_free (me); + } + list = next; + } +} + +static int +menu_del (char *path, char *label) +{ + GSList *list; + menu_entry *me; + + list = menu_list; + while (list) + { + me = list->data; + if (!menu_streq (me->label, label, 1) && !menu_streq (me->path, path, 1)) + { + menu_list = g_slist_remove (menu_list, me); + fe_menu_del (me); + menu_free (me); + /* delete this item's children, if any */ + menu_del_children (path, label); + return 1; + } + list = list->next; + } + + return 0; +} + +static char +menu_is_mainmenu_root (char *path, gint16 *offset) +{ + static const char *menus[] = {"\x4$TAB","\x5$TRAY","\x4$URL","\x5$NICK","\x5$CHAN"}; + int i; + + for (i = 0; i < 5; i++) + { + if (!strncmp (path, menus[i] + 1, menus[i][0])) + { + *offset = menus[i][0] + 1; /* number of bytes to offset the root */ + return 0; /* is not main menu */ + } + } + + *offset = 0; + return 1; /* is main menu */ +} + +static void +menu_add (char *path, char *label, char *cmd, char *ucmd, int pos, int state, int markup, int enable, int mod, int key, char *group, char *icon) +{ + menu_entry *me; + + /* already exists? */ + me = menu_entry_find (path, label); + if (me) + { + /* update only */ + me->state = state; + me->enable = enable; + fe_menu_update (me); + return; + } + + me = malloc (sizeof (menu_entry)); + me->pos = pos; + me->modifier = mod; + me->is_main = menu_is_mainmenu_root (path, &me->root_offset); + me->state = state; + me->markup = markup; + me->enable = enable; + me->key = key; + me->path = strdup (path); + me->label = NULL; + me->cmd = NULL; + me->ucmd = NULL; + me->group = NULL; + me->icon = NULL; + + if (label) + me->label = strdup (label); + if (cmd) + me->cmd = strdup (cmd); + if (ucmd) + me->ucmd = strdup (ucmd); + if (group) + me->group = strdup (group); + if (icon) + me->icon = strdup (icon); + + menu_list = g_slist_append (menu_list, me); + label = fe_menu_add (me); + if (label) + { + /* FE has given us a stripped label */ + free (me->label); + me->label = strdup (label); + g_free (label); /* this is from pango */ + } +} + +static int +cmd_menu (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int idx = 2; + int len; + int pos = 0xffff; + int state; + int toggle = FALSE; + int enable = TRUE; + int markup = FALSE; + int key = 0; + int mod = 0; + char *label; + char *group = NULL; + char *icon = NULL; + + if (!word[2][0] || !word[3][0]) + return FALSE; + + /* -eX enabled or not? */ + if (word[idx][0] == '-' && word[idx][1] == 'e') + { + enable = atoi (word[idx] + 2); + idx++; + } + + /* -i<ICONFILE> */ + if (word[idx][0] == '-' && word[idx][1] == 'i') + { + icon = word[idx] + 2; + idx++; + } + + /* -k<mod>,<key> key binding */ + if (word[idx][0] == '-' && word[idx][1] == 'k') + { + char *comma = strchr (word[idx], ','); + if (!comma) + return FALSE; + mod = atoi (word[idx] + 2); + key = atoi (comma + 1); + idx++; + } + + /* -m to specify PangoMarkup language */ + if (word[idx][0] == '-' && word[idx][1] == 'm') + { + markup = TRUE; + idx++; + } + + /* -pX to specify menu position */ + if (word[idx][0] == '-' && word[idx][1] == 'p') + { + pos = atoi (word[idx] + 2); + idx++; + } + + /* -rSTATE,GROUP to specify a radio item */ + if (word[idx][0] == '-' && word[idx][1] == 'r') + { + state = atoi (word[idx] + 2); + group = word[idx] + 4; + idx++; + } + + /* -tX to specify toggle item with default state */ + if (word[idx][0] == '-' && word[idx][1] == 't') + { + state = atoi (word[idx] + 2); + idx++; + toggle = TRUE; + } + + if (word[idx+1][0] == 0) + return FALSE; + + /* the path */ + path_part (word[idx+1], tbuf, 512); + len = strlen (tbuf); + if (len) + tbuf[len - 1] = 0; + + /* the name of the item */ + label = file_part (word[idx + 1]); + if (label[0] == '-' && label[1] == 0) + label = NULL; /* separator */ + + if (markup) + { + char *p; /* to force pango closing tags through */ + for (p = label; *p; p++) + if (*p == 3) + *p = '/'; + } + + if (!strcasecmp (word[idx], "ADD")) + { + if (toggle) + { + menu_add (tbuf, label, word[idx + 2], word[idx + 3], pos, state, markup, enable, mod, key, NULL, NULL); + } else + { + if (word[idx + 2][0]) + menu_add (tbuf, label, word[idx + 2], NULL, pos, state, markup, enable, mod, key, group, icon); + else + menu_add (tbuf, label, NULL, NULL, pos, state, markup, enable, mod, key, group, icon); + } + return TRUE; + } + + if (!strcasecmp (word[idx], "DEL")) + { + menu_del (tbuf, label); + return TRUE; + } + + return FALSE; +} + +static int +mkick_cb (struct User *user, multidata *data) +{ + if (!user->op && !user->me) + data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason); + return TRUE; +} + +static int +mkickops_cb (struct User *user, multidata *data) +{ + if (user->op && !user->me) + data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason); + return TRUE; +} + +static int +cmd_mkick (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + multidata data; + + data.sess = sess; + data.reason = word_eol[2]; + tree_foreach (sess->usertree, (tree_traverse_func *)mkickops_cb, &data); + tree_foreach (sess->usertree, (tree_traverse_func *)mkick_cb, &data); + + return TRUE; +} + +static int +cmd_devoice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'v', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_discon (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + sess->server->disconnect (sess, TRUE, -1); + return TRUE; +} + +static int +cmd_dns (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ +#ifdef WIN32 + PrintText (sess, "DNS is not implemented in Windows.\n"); + return TRUE; +#else + char *nick = word[2]; + struct User *user; + + if (*nick) + { + if (strchr (nick, '.') == NULL) + { + user = userlist_find (sess, nick); + if (user && user->hostname) + { + do_dns (sess, user->nick, user->hostname); + } else + { + sess->server->p_get_ip (sess->server, nick); + sess->server->doing_dns = TRUE; + } + } else + { + snprintf (tbuf, TBUFSIZE, "exec -d %s %s", prefs.dnsprogram, nick); + handle_command (sess, tbuf, FALSE); + } + return TRUE; + } + return FALSE; +#endif +} + +static int +cmd_echo (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + PrintText (sess, word_eol[2]); + return TRUE; +} + +#ifndef WIN32 + +static void +exec_check_process (struct session *sess) +{ + int val; + + if (sess->running_exec == NULL) + return; + val = waitpid (sess->running_exec->childpid, NULL, WNOHANG); + if (val == -1 || val > 0) + { + close (sess->running_exec->myfd); + fe_input_remove (sess->running_exec->iotag); + free (sess->running_exec); + sess->running_exec = NULL; + } +} + +#ifndef __EMX__ +static int +cmd_execs (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int r; + + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + r = kill (sess->running_exec->childpid, SIGSTOP); + if (r == -1) + PrintText (sess, "Error in kill(2)\n"); + + return TRUE; +} + +static int +cmd_execc (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int r; + + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + r = kill (sess->running_exec->childpid, SIGCONT); + if (r == -1) + PrintText (sess, "Error in kill(2)\n"); + + return TRUE; +} + +static int +cmd_execk (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int r; + + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + if (strcmp (word[2], "-9") == 0) + r = kill (sess->running_exec->childpid, SIGKILL); + else + r = kill (sess->running_exec->childpid, SIGTERM); + if (r == -1) + PrintText (sess, "Error in kill(2)\n"); + + return TRUE; +} + +/* OS/2 Can't have the /EXECW command because it uses pipe(2) not socketpair + and thus it is simplex --AGL */ +static int +cmd_execw (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int len; + char *temp; + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + len = strlen(word_eol[2]); + temp = malloc(len + 2); + sprintf(temp, "%s\n", word_eol[2]); + PrintText(sess, temp); + write(sess->running_exec->myfd, temp, len + 1); + free(temp); + + return TRUE; +} +#endif /* !__EMX__ */ + +/* convert ANSI escape color codes to mIRC codes */ + +static short escconv[] = +/* 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 */ +{ 1,4,3,5,2,10,6,1, 1,7,9,8,12,11,13,1 }; + +static void +exec_handle_colors (char *buf, int len) +{ + char numb[16]; + char *nbuf; + int i = 0, j = 0, k = 0, firstn = 0, col, colf = 0, colb = 0; + int esc = FALSE, backc = FALSE, bold = FALSE; + + /* any escape codes in this text? */ + if (strchr (buf, 27) == 0) + return; + + nbuf = malloc (len + 1); + + while (i < len) + { + switch (buf[i]) + { + case '\r': + break; + case 27: + esc = TRUE; + break; + case ';': + if (!esc) + goto norm; + backc = TRUE; + numb[k] = 0; + firstn = atoi (numb); + k = 0; + break; + case '[': + if (!esc) + goto norm; + break; + default: + if (esc) + { + if (buf[i] >= 'A' && buf[i] <= 'z') + { + if (buf[i] == 'm') + { + /* ^[[0m */ + if (k == 0 || (numb[0] == '0' && k == 1)) + { + nbuf[j] = '\017'; + j++; + bold = FALSE; + goto cont; + } + + numb[k] = 0; + col = atoi (numb); + backc = FALSE; + + if (firstn == 1) + bold = TRUE; + + if (firstn >= 30 && firstn <= 37) + colf = firstn - 30; + + if (col >= 40) + { + colb = col - 40; + backc = TRUE; + } + + if (col >= 30 && col <= 37) + colf = col - 30; + + if (bold) + colf += 8; + + if (backc) + { + colb = escconv[colb % 14]; + colf = escconv[colf % 14]; + j += sprintf (&nbuf[j], "\003%d,%02d", colf, colb); + } else + { + colf = escconv[colf % 14]; + j += sprintf (&nbuf[j], "\003%02d", colf); + } + } +cont: esc = FALSE; + backc = FALSE; + k = 0; + } else + { + if (isdigit ((unsigned char) buf[i]) && k < (sizeof (numb) - 1)) + { + numb[k] = buf[i]; + k++; + } + } + } else + { +norm: nbuf[j] = buf[i]; + j++; + } + } + i++; + } + + nbuf[j] = 0; + memcpy (buf, nbuf, j + 1); + free (nbuf); +} + +#ifndef HAVE_MEMRCHR +static void * +memrchr (const void *block, int c, size_t size) +{ + unsigned char *p; + + for (p = (unsigned char *)block + size; p != block; p--) + if (*p == c) + return p; + return 0; +} +#endif + +static gboolean +exec_data (GIOChannel *source, GIOCondition condition, struct nbexec *s) +{ + char *buf, *readpos, *rest; + int rd, len; + int sok = s->myfd; + + len = s->buffill; + if (len) { + /* append new data to buffered incomplete line */ + buf = malloc(len + 2050); + memcpy(buf, s->linebuf, len); + readpos = buf + len; + free(s->linebuf); + s->linebuf = NULL; + } + else + readpos = buf = malloc(2050); + + rd = read (sok, readpos, 2048); + if (rd < 1) + { + /* The process has died */ + kill(s->childpid, SIGKILL); + if (len) { + buf[len] = '\0'; + exec_handle_colors(buf, len); + if (s->tochannel) + { + /* must turn off auto-completion temporarily */ + unsigned int old = prefs.nickcompletion; + prefs.nickcompletion = 0; + handle_multiline (s->sess, buf, FALSE, TRUE); + prefs.nickcompletion = old; + } + else + PrintText (s->sess, buf); + } + free(buf); + waitpid (s->childpid, NULL, 0); + s->sess->running_exec = NULL; + fe_input_remove (s->iotag); + close (sok); + free (s); + return TRUE; + } + len += rd; + buf[len] = '\0'; + + rest = memrchr(buf, '\n', len); + if (rest) + rest++; + else + rest = buf; + if (*rest) { + s->buffill = len - (rest - buf); /* = strlen(rest) */ + s->linebuf = malloc(s->buffill); + memcpy(s->linebuf, rest, s->buffill); + *rest = '\0'; + len -= s->buffill; /* possibly 0 */ + } + else + s->buffill = 0; + + if (len) { + exec_handle_colors (buf, len); + if (s->tochannel) + handle_multiline (s->sess, buf, FALSE, TRUE); + else + PrintText (s->sess, buf); + } + + free(buf); + return TRUE; +} + +static int +cmd_exec (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int tochannel = FALSE; + char *cmd = word_eol[2]; + int fds[2], pid = 0; + struct nbexec *s; + int shell = TRUE; + int fd; + + if (*cmd) + { + exec_check_process (sess); + if (sess->running_exec != NULL) + { + EMIT_SIGNAL (XP_TE_ALREADYPROCESS, sess, NULL, NULL, NULL, NULL, 0); + return TRUE; + } + + if (!strcmp (word[2], "-d")) + { + if (!*word[3]) + return FALSE; + cmd = word_eol[3]; + shell = FALSE; + } + else if (!strcmp (word[2], "-o")) + { + if (!*word[3]) + return FALSE; + cmd = word_eol[3]; + tochannel = TRUE; + } + + if (shell) + { + if (access ("/bin/sh", X_OK) != 0) + { + fe_message (_("I need /bin/sh to run!\n"), FE_MSG_ERROR); + return TRUE; + } + } + +#ifdef __EMX__ /* if os/2 */ + if (pipe (fds) < 0) + { + PrintText (sess, "Pipe create error\n"); + return FALSE; + } + setmode (fds[0], O_BINARY); + setmode (fds[1], O_BINARY); +#else + if (socketpair (PF_UNIX, SOCK_STREAM, 0, fds) == -1) + { + PrintText (sess, "socketpair(2) failed\n"); + return FALSE; + } +#endif + s = (struct nbexec *) malloc (sizeof (struct nbexec)); + memset(s, 0, sizeof(*s)); + s->myfd = fds[0]; + s->tochannel = tochannel; + s->sess = sess; + + pid = fork (); + if (pid == 0) + { + /* This is the child's context */ + close (0); + close (1); + close (2); + /* Close parent's end of pipe */ + close(s->myfd); + /* Copy the child end of the pipe to stdout and stderr */ + dup2 (fds[1], 1); + dup2 (fds[1], 2); + /* Also copy it to stdin so we can write to it */ + dup2 (fds[1], 0); + /* Now close all open file descriptors except stdin, stdout and stderr */ + for (fd = 3; fd < 1024; fd++) close(fd); + /* Now we call /bin/sh to run our cmd ; made it more friendly -DC1 */ + if (shell) + { + execl ("/bin/sh", "sh", "-c", cmd, NULL); + } else + { + char **argv; + int argc; + + my_poptParseArgvString (cmd, &argc, &argv); + execvp (argv[0], argv); + } + /* not reached unless error */ + /*printf("exec error\n");*/ + fflush (stdout); + fflush (stdin); + _exit (0); + } + if (pid == -1) + { + /* Parent context, fork() failed */ + + PrintText (sess, "Error in fork(2)\n"); + close(fds[0]); + close(fds[1]); + } else + { + /* Parent path */ + close(fds[1]); + s->childpid = pid; + s->iotag = fe_input_add (s->myfd, FIA_READ|FIA_EX, exec_data, s); + sess->running_exec = s; + return TRUE; + } + } + return FALSE; +} + +#endif + +static int +cmd_flushq (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + sprintf (tbuf, "Flushing server send queue, %d bytes.\n", sess->server->sendq_len); + PrintText (sess, tbuf); + sess->server->flush_queue (sess->server); + return TRUE; +} + +static int +cmd_quit (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + sess->quitreason = word_eol[2]; + sess->server->disconnect (sess, TRUE, -1); + sess->quitreason = NULL; + return 2; +} + +static int +cmd_gate (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *server_name = word[2]; + server *serv = sess->server; + if (*server_name) + { + char *port = word[3]; +#ifdef USE_OPENSSL + serv->use_ssl = FALSE; +#endif + server_fill_her_up (serv); + if (*port) + serv->connect (serv, server_name, atoi (port), TRUE); + else + serv->connect (serv, server_name, 23, TRUE); + return TRUE; + } + return FALSE; +} + +typedef struct +{ + char *cmd; + session *sess; +} getvalinfo; + +static void +get_int_cb (int cancel, int val, getvalinfo *info) +{ + char buf[512]; + + if (!cancel) + { + snprintf (buf, sizeof (buf), "%s %d", info->cmd, val); + if (is_session (info->sess)) + handle_command (info->sess, buf, FALSE); + } + + free (info->cmd); + free (info); +} + +static int +cmd_getint (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + getvalinfo *info; + + if (!word[4][0]) + return FALSE; + + info = malloc (sizeof (*info)); + info->cmd = strdup (word[3]); + info->sess = sess; + + fe_get_int (word[4], atoi (word[2]), get_int_cb, info); + + return TRUE; +} + +static void +get_file_cb (char *cmd, char *file) +{ + char buf[1024 + 128]; + + /* execute the command once per file, then once more with + no args */ + if (file) + { + snprintf (buf, sizeof (buf), "%s %s", cmd, file); + handle_command (current_sess, buf, FALSE); + } + else + { + handle_command (current_sess, cmd, FALSE); + free (cmd); + } +} + +static int +cmd_getfile (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int idx = 2; + int flags = 0; + + if (!word[3][0]) + return FALSE; + + if (!strcmp (word[2], "-folder")) + { + flags |= FRF_CHOOSEFOLDER; + idx++; + } + + if (!strcmp (word[idx], "-multi")) + { + flags |= FRF_MULTIPLE; + idx++; + } + + if (!strcmp (word[idx], "-save")) + { + flags |= FRF_WRITE; + idx++; + } + + fe_get_file (word[idx+1], word[idx+2], (void *)get_file_cb, strdup (word[idx]), flags); + + return TRUE; +} + +static void +get_str_cb (int cancel, char *val, getvalinfo *info) +{ + char buf[512]; + + if (!cancel) + { + snprintf (buf, sizeof (buf), "%s %s", info->cmd, val); + if (is_session (info->sess)) + handle_command (info->sess, buf, FALSE); + } + + free (info->cmd); + free (info); +} + +static int +cmd_getstr (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + getvalinfo *info; + + if (!word[4][0]) + return FALSE; + + info = malloc (sizeof (*info)); + info->cmd = strdup (word[3]); + info->sess = sess; + + fe_get_str (word[4], word[2], get_str_cb, info); + + return TRUE; +} + +static int +cmd_ghost (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (!word[2][0]) + return FALSE; + + sess->server->p_ns_ghost (sess->server, word[2], word[3]); + return TRUE; +} + +static int +cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + switch (str_ihash (word[2])) + { + case 0x058b836e: fe_ctrl_gui (sess, 8, 0); break; /* APPLY */ + case 0xac1eee45: fe_ctrl_gui (sess, 7, 2); break; /* ATTACH */ + case 0x05a72f63: fe_ctrl_gui (sess, 4, atoi (word[3])); break; /* COLOR */ + case 0xb06a1793: fe_ctrl_gui (sess, 7, 1); break; /* DETACH */ + case 0x05cfeff0: fe_ctrl_gui (sess, 3, 0); break; /* FLASH */ + case 0x05d154d8: fe_ctrl_gui (sess, 2, 0); break; /* FOCUS */ + case 0x0030dd42: fe_ctrl_gui (sess, 0, 0); break; /* HIDE */ + case 0x61addbe3: fe_ctrl_gui (sess, 5, 0); break; /* ICONIFY */ + case 0xc0851aaa: fe_message (word[3], FE_MSG_INFO|FE_MSG_MARKUP); break; /* MSGBOX */ + case 0x0035dafd: fe_ctrl_gui (sess, 1, 0); break; /* SHOW */ + case 0x0033155f: /* MENU */ + if (!strcasecmp (word[3], "TOGGLE")) + fe_ctrl_gui (sess, 6, 0); + else + return FALSE; + break; + default: + return FALSE; + } + + return TRUE; +} + +typedef struct +{ + int longfmt; + int i, t; + char *buf; +} help_list; + +static void +show_help_line (session *sess, help_list *hl, char *name, char *usage) +{ + int j, len, max; + char *p; + + if (name[0] == '.') /* hidden command? */ + return; + + if (hl->longfmt) /* long format for /HELP -l */ + { + if (!usage || usage[0] == 0) + PrintTextf (sess, " \0034%s\003 :\n", name); + else + PrintTextf (sess, " \0034%s\003 : %s\n", name, _(usage)); + return; + } + + /* append the name into buffer, but convert to uppercase */ + len = strlen (hl->buf); + p = name; + while (*p) + { + hl->buf[len] = toupper ((unsigned char) *p); + len++; + p++; + } + hl->buf[len] = 0; + + hl->t++; + if (hl->t == 5) + { + hl->t = 0; + strcat (hl->buf, "\n"); + PrintText (sess, hl->buf); + hl->buf[0] = ' '; + hl->buf[1] = ' '; + hl->buf[2] = 0; + } else + { + /* append some spaces after the command name */ + max = strlen (name); + if (max < 10) + { + max = 10 - max; + for (j = 0; j < max; j++) + { + hl->buf[len] = ' '; + len++; + hl->buf[len] = 0; + } + } + } +} + +static int +cmd_help (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 0, longfmt = 0; + char *helpcmd = ""; + GSList *list; + + if (tbuf) + helpcmd = word[2]; + if (*helpcmd && strcmp (helpcmd, "-l") == 0) + longfmt = 1; + + if (*helpcmd && !longfmt) + { + help (sess, tbuf, helpcmd, FALSE); + } else + { + struct popup *pop; + char *buf = malloc (4096); + help_list hl; + + hl.longfmt = longfmt; + hl.buf = buf; + + PrintTextf (sess, "\n%s\n\n", _("Commands Available:")); + buf[0] = ' '; + buf[1] = ' '; + buf[2] = 0; + hl.t = 0; + hl.i = 0; + while (xc_cmds[i].name) + { + show_help_line (sess, &hl, xc_cmds[i].name, xc_cmds[i].help); + i++; + } + strcat (buf, "\n"); + PrintText (sess, buf); + + PrintTextf (sess, "\n%s\n\n", _("User defined commands:")); + buf[0] = ' '; + buf[1] = ' '; + buf[2] = 0; + hl.t = 0; + hl.i = 0; + list = command_list; + while (list) + { + pop = list->data; + show_help_line (sess, &hl, pop->name, pop->cmd); + list = list->next; + } + strcat (buf, "\n"); + PrintText (sess, buf); + + PrintTextf (sess, "\n%s\n\n", _("Plugin defined commands:")); + buf[0] = ' '; + buf[1] = ' '; + buf[2] = 0; + hl.t = 0; + hl.i = 0; + plugin_command_foreach (sess, &hl, (void *)show_help_line); + strcat (buf, "\n"); + PrintText (sess, buf); + free (buf); + + PrintTextf (sess, "\n%s\n\n", _("Type /HELP <command> for more information, or /HELP -l")); + } + return TRUE; +} + +static int +cmd_id (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (word[2][0]) + { + sess->server->p_ns_identify (sess->server, word[2]); + return TRUE; + } + + return FALSE; +} + +static int +cmd_ignore (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i; + int type = 0; + int quiet = 0; + char *mask; + + if (!*word[2]) + { + ignore_showlist (sess); + return TRUE; + } + if (!*word[3]) + return FALSE; + + i = 3; + while (1) + { + if (!*word[i]) + { + if (type == 0) + return FALSE; + + mask = word[2]; + if (strchr (mask, '?') == NULL && + strchr (mask, '*') == NULL && + userlist_find (sess, mask)) + { + mask = tbuf; + snprintf (tbuf, TBUFSIZE, "%s!*@*", word[2]); + } + + i = ignore_add (mask, type); + if (quiet) + return TRUE; + switch (i) + { + case 1: + EMIT_SIGNAL (XP_TE_IGNOREADD, sess, mask, NULL, NULL, NULL, 0); + break; + case 2: /* old ignore changed */ + EMIT_SIGNAL (XP_TE_IGNORECHANGE, sess, mask, NULL, NULL, NULL, 0); + } + return TRUE; + } + if (!strcasecmp (word[i], "UNIGNORE")) + type |= IG_UNIG; + else if (!strcasecmp (word[i], "ALL")) + type |= IG_PRIV | IG_NOTI | IG_CHAN | IG_CTCP | IG_INVI | IG_DCC; + else if (!strcasecmp (word[i], "PRIV")) + type |= IG_PRIV; + else if (!strcasecmp (word[i], "NOTI")) + type |= IG_NOTI; + else if (!strcasecmp (word[i], "CHAN")) + type |= IG_CHAN; + else if (!strcasecmp (word[i], "CTCP")) + type |= IG_CTCP; + else if (!strcasecmp (word[i], "INVI")) + type |= IG_INVI; + else if (!strcasecmp (word[i], "QUIET")) + quiet = 1; + else if (!strcasecmp (word[i], "NOSAVE")) + type |= IG_NOSAVE; + else if (!strcasecmp (word[i], "DCC")) + type |= IG_DCC; + else + { + sprintf (tbuf, _("Unknown arg '%s' ignored."), word[i]); + PrintText (sess, tbuf); + } + i++; + } +} + +static int +cmd_invite (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (!*word[2]) + return FALSE; + if (*word[3]) + sess->server->p_invite (sess->server, word[3], word[2]); + else + sess->server->p_invite (sess->server, sess->channel, word[2]); + return TRUE; +} + +static int +cmd_join (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *chan = word[2]; + if (*chan) + { + char *po, *pass = word[3]; + sess->server->p_join (sess->server, chan, pass); + if (sess->channel[0] == 0 && sess->waitchannel[0]) + { + po = strchr (chan, ','); + if (po) + *po = 0; + safe_strcpy (sess->waitchannel, chan, CHANLEN); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_kick (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + char *reason = word_eol[3]; + if (*nick) + { + sess->server->p_kick (sess->server, sess->channel, nick, reason); + return TRUE; + } + return FALSE; +} + +static int +cmd_kickban (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + char *reason = word_eol[3]; + struct User *user; + + if (*nick) + { + /* if the reason is a 1 digit number, treat it as a bantype */ + + user = userlist_find (sess, nick); + + if (isdigit ((unsigned char) reason[0]) && reason[1] == 0) + { + ban (sess, tbuf, nick, reason, (user && user->op)); + reason[0] = 0; + } else + ban (sess, tbuf, nick, "", (user && user->op)); + + sess->server->p_kick (sess->server, sess->channel, nick, reason); + + return TRUE; + } + return FALSE; +} + +static int +cmd_killall (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + xchat_exit(); + return 2; +} + +static int +cmd_lagcheck (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + lag_check (); + return TRUE; +} + +static void +lastlog (session *sess, char *search, gboolean regexp) +{ + session *lastlog_sess; + + if (!is_session (sess)) + return; + + lastlog_sess = find_dialog (sess->server, "(lastlog)"); + if (!lastlog_sess) + lastlog_sess = new_ircwindow (sess->server, "(lastlog)", SESS_DIALOG, 0); + + lastlog_sess->lastlog_sess = sess; + lastlog_sess->lastlog_regexp = regexp; /* remember the search type */ + + fe_text_clear (lastlog_sess, 0); + fe_lastlog (sess, lastlog_sess, search, regexp); +} + +static int +cmd_lastlog (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + { + if (!strcmp (word[2], "-r")) + lastlog (sess, word_eol[3], TRUE); + else + lastlog (sess, word_eol[2], FALSE); + return TRUE; + } + + return FALSE; +} + +static int +cmd_list (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + sess->server->p_list_channels (sess->server, word_eol[2], 1); + + return TRUE; +} + +gboolean +load_perform_file (session *sess, char *file) +{ + char tbuf[1024 + 4]; + char *nl; + FILE *fp; + + fp = xchat_fopen_file (file, "r", XOF_FULLPATH); + if (!fp) + return FALSE; + + tbuf[1024] = 0; + while (fgets (tbuf, 1024, fp)) + { + nl = strchr (tbuf, '\n'); + if (nl == tbuf) /* skip empty commands */ + continue; + if (nl) + *nl = 0; + if (tbuf[0] == prefs.cmdchar[0]) + handle_command (sess, tbuf + 1, TRUE); + else + handle_command (sess, tbuf, TRUE); + } + fclose (fp); + return TRUE; +} + +static int +cmd_load (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *error, *arg, *file; + int len; + + if (!word[2][0]) + return FALSE; + + if (strcmp (word[2], "-e") == 0) + { + file = expand_homedir (word[3]); + if (!load_perform_file (sess, file)) + { + PrintTextf (sess, _("Cannot access %s\n"), file); + PrintText (sess, errorstring (errno)); + } + free (file); + return TRUE; + } + +#ifdef USE_PLUGIN + len = strlen (word[2]); +#ifdef WIN32 + if (len > 4 && strcasecmp (".dll", word[2] + len - 4) == 0) +#else +#if defined(__hpux) + if (len > 3 && strcasecmp (".sl", word[2] + len - 3) == 0) +#else + if (len > 3 && strcasecmp (".so", word[2] + len - 3) == 0) +#endif +#endif + { + arg = NULL; + if (word_eol[3][0]) + arg = word_eol[3]; + + file = expand_homedir (word[2]); + error = plugin_load (sess, file, arg); + free (file); + + if (error) + PrintText (sess, error); + + return TRUE; + } +#endif + + sprintf (tbuf, "Unknown file type %s. Maybe you need to install the Perl or Python plugin?\n", word[2]); + PrintText (sess, tbuf); + + return FALSE; +} + +static int +cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *act = word_eol[2]; + + if (!(*act)) + return FALSE; + + if (sess->type == SESS_SERVER) + { + notj_msg (sess); + return TRUE; + } + + snprintf (tbuf, TBUFSIZE, "\001ACTION %s\001\r", act); + /* first try through DCC CHAT */ + if (dcc_write_chat (sess->channel, tbuf)) + { + /* print it to screen */ + inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE); + } else + { + /* DCC CHAT failed, try through server */ + if (sess->server->connected) + { + sess->server->p_action (sess->server, sess->channel, act); + /* print it to screen */ + inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE); + } else + { + notc_msg (sess); + } + } + + return TRUE; +} + +static int +cmd_mode (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + /* +channel channels are dying, let those servers whine about modes. + * return info about current channel if available and no info is given */ + if ((*word[2] == '+') || (*word[2] == 0) || (!is_channel(sess->server, word[2]) && + !(rfc_casecmp(sess->server->nick, word[2]) == 0))) + { + if(sess->channel[0] == 0) + return FALSE; + sess->server->p_mode (sess->server, sess->channel, word_eol[2]); + } + else + sess->server->p_mode (sess->server, word[2], word_eol[3]); + return TRUE; +} + +static int +mop_cb (struct User *user, multidata *data) +{ + if (!user->op) + { + data->nicks[data->i] = user->nick; + data->i++; + } + return TRUE; +} + +static int +cmd_mop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char **nicks = malloc (sizeof (char *) * (sess->total - sess->ops)); + multidata data; + + data.nicks = nicks; + data.i = 0; + tree_foreach (sess->usertree, (tree_traverse_func *)mop_cb, &data); + send_channel_modes (sess, tbuf, nicks, 0, data.i, '+', 'o', 0); + + free (nicks); + + return TRUE; +} + +static int +cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + char *msg = word_eol[3]; + struct session *newsess; + + if (*nick) + { + if (*msg) + { + if (strcmp (nick, ".") == 0) + { /* /msg the last nick /msg'ed */ + if (sess->lastnick[0]) + nick = sess->lastnick; + } else + { + safe_strcpy (sess->lastnick, nick, NICKLEN); /* prime the last nick memory */ + } + + if (*nick == '=') + { + nick++; + if (!dcc_write_chat (nick, msg)) + { + EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0); + return TRUE; + } + } else + { + if (!sess->server->connected) + { + notc_msg (sess); + return TRUE; + } + sess->server->p_message (sess->server, nick, msg); + } + newsess = find_dialog (sess->server, nick); + if (!newsess) + newsess = find_channel (sess->server, nick); + if (newsess) + inbound_chanmsg (newsess->server, NULL, newsess->channel, + newsess->server->nick, msg, TRUE, FALSE); + else + { + /* mask out passwords */ + if (strcasecmp (nick, "nickserv") == 0 && + strncasecmp (msg, "identify ", 9) == 0) + msg = "identify ****"; + EMIT_SIGNAL (XP_TE_MSGSEND, sess, nick, msg, NULL, NULL, 0); + } + + return TRUE; + } + } + return FALSE; +} + +static int +cmd_names (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word[2]) + sess->server->p_names (sess->server, word[2]); + else + sess->server->p_names (sess->server, sess->channel); + return TRUE; +} + +static int +cmd_nctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[3]) + { + sess->server->p_nctcp (sess->server, word[2], word_eol[3]); + return TRUE; + } + return FALSE; +} + +static int +cmd_newserver (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + if (strcmp (word[2], "-noconnect") == 0) + { + new_ircwindow (NULL, word[3], SESS_SERVER, 0); + return TRUE; + } + + sess = new_ircwindow (NULL, NULL, SESS_SERVER, 0); + cmd_server (sess, tbuf, word, word_eol); + return TRUE; +} + +static int +cmd_nick (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + if (*nick) + { + if (sess->server->connected) + sess->server->p_change_nick (sess->server, nick); + else + inbound_newnick (sess->server, sess->server->nick, nick, TRUE); + return TRUE; + } + return FALSE; +} + +static int +cmd_notice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word[2] && *word_eol[3]) + { + sess->server->p_notice (sess->server, word[2], word_eol[3]); + EMIT_SIGNAL (XP_TE_NOTICESEND, sess, word[2], word_eol[3], NULL, NULL, 0); + return TRUE; + } + return FALSE; +} + +static int +cmd_notify (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 1; + char *net = NULL; + + if (*word[2]) + { + if (strcmp (word[2], "-n") == 0) /* comma sep network list */ + { + net = word[3]; + i += 2; + } + + while (1) + { + i++; + if (!*word[i]) + break; + if (notify_deluser (word[i])) + { + EMIT_SIGNAL (XP_TE_DELNOTIFY, sess, word[i], NULL, NULL, NULL, 0); + return TRUE; + } + + if (net && strcmp (net, "ASK") == 0) + fe_notify_ask (word[i], NULL); + else + { + notify_adduser (word[i], net); + EMIT_SIGNAL (XP_TE_ADDNOTIFY, sess, word[i], NULL, NULL, NULL, 0); + } + } + } else + notify_showlist (sess); + return TRUE; +} + +static int +cmd_op (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '+', 'o', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_part (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *chan = word[2]; + char *reason = word_eol[3]; + if (!*chan) + chan = sess->channel; + if ((*chan) && is_channel (sess->server, chan)) + { + if (reason[0] == 0) + reason = NULL; + server_sendpart (sess->server, chan, reason); + return TRUE; + } + return FALSE; +} + +static int +cmd_ping (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char timestring[64]; + unsigned long tim; + char *to = word[2]; + + tim = make_ping_time (); + + snprintf (timestring, sizeof (timestring), "%lu", tim); + sess->server->p_ping (sess->server, to, timestring); + + return TRUE; +} + +void +open_query (server *serv, char *nick, gboolean focus_existing) +{ + session *sess; + + sess = find_dialog (serv, nick); + if (!sess) + new_ircwindow (serv, nick, SESS_DIALOG, 1); + else if (focus_existing) + fe_ctrl_gui (sess, 2, 0); /* bring-to-front */ +} + +static int +cmd_query (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + gboolean focus = TRUE; + + if (strcmp (word[2], "-nofocus") == 0) + { + nick = word[3]; + focus = FALSE; + } + + if (*nick && !is_channel (sess->server, nick)) + { + open_query (sess->server, nick, focus); + return TRUE; + } + return FALSE; +} + +static int +cmd_quote (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *raw = word_eol[2]; + + return sess->server->p_raw (sess->server, raw); +} + +static int +cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int tmp = prefs.recon_delay; + GSList *list; + server *serv = sess->server; + + prefs.recon_delay = 0; + + if (!strcasecmp (word[2], "ALL")) + { + list = serv_list; + while (list) + { + serv = list->data; + if (serv->connected) + serv->auto_reconnect (serv, TRUE, -1); + list = list->next; + } + } + /* If it isn't "ALL" and there is something + there it *should* be a server they are trying to connect to*/ + else if (*word[2]) + { + int offset = 0; +#ifdef USE_OPENSSL + int use_ssl = FALSE; + + if (strcmp (word[2], "-ssl") == 0) + { + use_ssl = TRUE; + offset++; /* args move up by 1 word */ + } + serv->use_ssl = use_ssl; + serv->accept_invalid_cert = TRUE; +#endif + + if (*word[4+offset]) + safe_strcpy (serv->password, word[4+offset], sizeof (serv->password)); + if (*word[3+offset]) + serv->port = atoi (word[3+offset]); + safe_strcpy (serv->hostname, word[2+offset], sizeof (serv->hostname)); + serv->auto_reconnect (serv, TRUE, -1); + } + else + { + serv->auto_reconnect (serv, TRUE, -1); + } + prefs.recon_delay = tmp; + + return TRUE; +} + +static int +cmd_recv (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + { + sess->server->p_inline (sess->server, word_eol[2], strlen (word_eol[2])); + return TRUE; + } + + return FALSE; +} + +static int +cmd_say (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *speech = word_eol[2]; + if (*speech) + { + handle_say (sess, speech, FALSE); + return TRUE; + } + return FALSE; +} + +static int +cmd_send (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + guint32 addr; + socklen_t len; + struct sockaddr_in SAddr; + + if (!word[2][0]) + return FALSE; + + addr = dcc_get_my_address (); + if (addr == 0) + { + /* use the one from our connected server socket */ + memset (&SAddr, 0, sizeof (struct sockaddr_in)); + len = sizeof (SAddr); + getsockname (sess->server->sok, (struct sockaddr *) &SAddr, &len); + addr = SAddr.sin_addr.s_addr; + } + addr = ntohl (addr); + + if ((addr & 0xffff0000) == 0xc0a80000 || /* 192.168.x.x */ + (addr & 0xff000000) == 0x0a000000) /* 10.x.x.x */ + /* we got a private net address, let's PSEND or it'll fail */ + snprintf (tbuf, 512, "DCC PSEND %s", word_eol[2]); + else + snprintf (tbuf, 512, "DCC SEND %s", word_eol[2]); + + handle_command (sess, tbuf, FALSE); + + return TRUE; +} + +static int +cmd_setcursor (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int delta = FALSE; + + if (*word[2]) + { + if (word[2][0] == '-' || word[2][0] == '+') + delta = TRUE; + fe_set_inputbox_cursor (sess, delta, atoi (word[2])); + return TRUE; + } + + return FALSE; +} + +static int +cmd_settab (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + { + strcpy (tbuf, sess->channel); + safe_strcpy (sess->channel, word_eol[2], CHANLEN); + fe_set_channel (sess); + strcpy (sess->channel, tbuf); + } + + return TRUE; +} + +static int +cmd_settext (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + fe_set_inputbox_contents (sess, word_eol[2]); + return TRUE; +} + +static int +cmd_splay (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word[2]) + { + sound_play (word[2], FALSE); + return TRUE; + } + + return FALSE; +} + +static int +parse_irc_url (char *url, char *server_name[], char *port[], char *channel[], int *use_ssl) +{ + char *co; +#ifdef USE_OPENSSL + if (strncasecmp ("ircs://", url, 7) == 0) + { + *use_ssl = TRUE; + *server_name = url + 7; + goto urlserv; + } +#endif + + if (strncasecmp ("irc://", url, 6) == 0) + { + *server_name = url + 6; +#ifdef USE_OPENSSL +urlserv: +#endif + /* check for port */ + co = strchr (*server_name, ':'); + if (co) + { + *port = co + 1; + *co = 0; + } else + co = *server_name; + /* check for channel - mirc style */ + co = strchr (co + 1, '/'); + if (co) + { + *co = 0; + co++; + if (*co == '#') + *channel = co+1; + else + *channel = co; + + } + return TRUE; + } + return FALSE; +} + +static int +cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int offset = 0; + char *server_name = NULL; + char *port = NULL; + char *pass = NULL; + char *channel = NULL; + int use_ssl = FALSE; + int is_url = TRUE; + server *serv = sess->server; + +#ifdef USE_OPENSSL + /* BitchX uses -ssl, mIRC uses -e, let's support both */ + if (strcmp (word[2], "-ssl") == 0 || strcmp (word[2], "-e") == 0) + { + use_ssl = TRUE; + offset++; /* args move up by 1 word */ + } +#endif + + if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &use_ssl)) + { + is_url = FALSE; + server_name = word[2 + offset]; + } + if (port) + pass = word[3 + offset]; + else + { + port = word[3 + offset]; + pass = word[4 + offset]; + } + + if (!(*server_name)) + return FALSE; + + sess->server->network = NULL; + + /* dont clear it for /servchan */ + if (strncasecmp (word_eol[1], "SERVCHAN ", 9)) + sess->willjoinchannel[0] = 0; + + if (channel) + { + sess->willjoinchannel[0] = '#'; + safe_strcpy ((sess->willjoinchannel + 1), channel, (CHANLEN - 1)); + } + + /* support +7000 style ports like mIRC */ + if (port[0] == '+') + { + port++; +#ifdef USE_OPENSSL + use_ssl = TRUE; +#endif + } + + if (*pass) + { + safe_strcpy (serv->password, pass, sizeof (serv->password)); + } +#ifdef USE_OPENSSL + serv->use_ssl = use_ssl; + serv->accept_invalid_cert = TRUE; +#endif + + /* try to connect by Network name */ + if (servlist_connect_by_netname (sess, server_name, !is_url)) + return TRUE; + + if (*port) + { + serv->connect (serv, server_name, atoi (port), FALSE); + } else + { + /* -1 for default port */ + serv->connect (serv, server_name, -1, FALSE); + } + + /* try to associate this connection with a listed network */ + if (!serv->network) + /* search for this hostname in the entire server list */ + serv->network = servlist_net_find_from_server (server_name); + /* may return NULL, but that's OK */ + + return TRUE; +} + +static int +cmd_servchan (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + int offset = 0; + +#ifdef USE_OPENSSL + if (strcmp (word[2], "-ssl") == 0) + offset++; +#endif + + if (*word[4 + offset]) + { + safe_strcpy (sess->willjoinchannel, word[4 + offset], CHANLEN); + return cmd_server (sess, tbuf, word, word_eol); + } + + return FALSE; +} + +static int +cmd_topic (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (word[2][0] && is_channel (sess->server, word[2])) + sess->server->p_topic (sess->server, word[2], word_eol[3]); + else + sess->server->p_topic (sess->server, sess->channel, word_eol[2]); + return TRUE; +} + +static int +cmd_tray (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (strcmp (word[2], "-b") == 0) + { + fe_tray_set_balloon (word[3], word[4][0] ? word[4] : NULL); + return TRUE; + } + + if (strcmp (word[2], "-t") == 0) + { + fe_tray_set_tooltip (word[3][0] ? word[3] : NULL); + return TRUE; + } + + if (strcmp (word[2], "-i") == 0) + { + fe_tray_set_icon (atoi (word[3])); + return TRUE; + } + + if (strcmp (word[2], "-f") != 0) + return FALSE; + + if (!word[3][0]) + { + fe_tray_set_file (NULL); /* default xchat icon */ + return TRUE; + } + + if (!word[4][0]) + { + fe_tray_set_file (word[3]); /* fixed custom icon */ + return TRUE; + } + + /* flash between 2 icons */ + fe_tray_set_flash (word[4], word[5][0] ? word[5] : NULL, atoi (word[3])); + return TRUE; +} + +static int +cmd_unignore (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + char *mask = word[2]; + char *arg = word[3]; + if (*mask) + { + if (ignore_del (mask, NULL)) + { + if (strcasecmp (arg, "QUIET")) + EMIT_SIGNAL (XP_TE_IGNOREREMOVE, sess, mask, NULL, NULL, NULL, 0); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_unload (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ +#ifdef USE_PLUGIN + int len, by_file = FALSE; + + len = strlen (word[2]); +#ifdef WIN32 + if (len > 4 && strcasecmp (word[2] + len - 4, ".dll") == 0) +#else +#if defined(__hpux) + if (len > 3 && strcasecmp (word[2] + len - 3, ".sl") == 0) +#else + if (len > 3 && strcasecmp (word[2] + len - 3, ".so") == 0) +#endif +#endif + by_file = TRUE; + + switch (plugin_kill (word[2], by_file)) + { + case 0: + PrintText (sess, _("No such plugin found.\n")); + break; + case 1: + return TRUE; + case 2: + PrintText (sess, _("That plugin is refusing to unload.\n")); + break; + } +#endif + + return FALSE; +} + +static server * +find_server_from_hostname (char *hostname) +{ + GSList *list = serv_list; + server *serv; + + while (list) + { + serv = list->data; + if (!strcasecmp (hostname, serv->hostname) && serv->connected) + return serv; + list = list->next; + } + + return NULL; +} + +static server * +find_server_from_net (void *net) +{ + GSList *list = serv_list; + server *serv; + + while (list) + { + serv = list->data; + if (serv->network == net && serv->connected) + return serv; + list = list->next; + } + + return NULL; +} + +static void +url_join_only (server *serv, char *tbuf, char *channel) +{ + /* already connected, JOIN only. FIXME: support keys? */ + tbuf[0] = '#'; + /* tbuf is 4kb */ + safe_strcpy ((tbuf + 1), channel, 256); + serv->p_join (serv, tbuf, ""); +} + +static int +cmd_url (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (word[2][0]) + { + char *server_name = NULL; + char *port = NULL; + char *channel = NULL; + char *url = g_strdup (word[2]); + int use_ssl = FALSE; + void *net; + server *serv; + + if (parse_irc_url (url, &server_name, &port, &channel, &use_ssl)) + { + /* maybe we're already connected to this net */ + + /* check for "FreeNode" */ + net = servlist_net_find (server_name, NULL, strcasecmp); + /* check for "irc.eu.freenode.net" */ + if (!net) + net = servlist_net_find_from_server (server_name); + + if (net) + { + /* found the network, but are we connected? */ + serv = find_server_from_net (net); + if (serv) + { + url_join_only (serv, tbuf, channel); + g_free (url); + return TRUE; + } + } + else + { + /* an un-listed connection */ + serv = find_server_from_hostname (server_name); + if (serv) + { + url_join_only (serv, tbuf, channel); + g_free (url); + return TRUE; + } + } + + /* not connected to this net, open new window */ + cmd_newserver (sess, tbuf, word, word_eol); + + } else + fe_open_url (word[2]); + g_free (url); + return TRUE; + } + + return FALSE; +} + +static int +userlist_cb (struct User *user, session *sess) +{ + time_t lt; + + if (!user->lasttalk) + lt = 0; + else + lt = time (0) - user->lasttalk; + PrintTextf (sess, + "\00306%s\t\00314[\00310%-38s\00314] \017ov\0033=\017%d%d away=%u lt\0033=\017%d\n", + user->nick, user->hostname, user->op, user->voice, user->away, lt); + + return TRUE; +} + +static int +cmd_uselect (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int idx = 2; + int clear = TRUE; + int scroll = FALSE; + + if (strcmp (word[2], "-a") == 0) /* ADD (don't clear selections) */ + { + clear = FALSE; + idx++; + } + if (strcmp (word[idx], "-s") == 0) /* SCROLL TO */ + { + scroll = TRUE; + idx++; + } + /* always valid, no args means clear the selection list */ + fe_uselect (sess, word + idx, clear, scroll); + return TRUE; +} + +static int +cmd_userlist (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + tree_foreach (sess->usertree, (tree_traverse_func *)userlist_cb, sess); + return TRUE; +} + +static int +wallchop_cb (struct User *user, multidata *data) +{ + if (user->op) + { + if (data->i) + strcat (data->tbuf, ","); + strcat (data->tbuf, user->nick); + data->i++; + } + if (data->i == 5) + { + data->i = 0; + sprintf (data->tbuf + strlen (data->tbuf), + " :[@%s] %s", data->sess->channel, data->reason); + data->sess->server->p_raw (data->sess->server, data->tbuf); + strcpy (data->tbuf, "NOTICE "); + } + + return TRUE; +} + +static int +cmd_wallchop (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + multidata data; + + if (!(*word_eol[2])) + return FALSE; + + strcpy (tbuf, "NOTICE "); + + data.reason = word_eol[2]; + data.tbuf = tbuf; + data.i = 0; + data.sess = sess; + tree_foreach (sess->usertree, (tree_traverse_func*)wallchop_cb, &data); + + if (data.i) + { + sprintf (tbuf + strlen (tbuf), + " :[@%s] %s", sess->channel, word_eol[2]); + sess->server->p_raw (sess->server, tbuf); + } + + return TRUE; +} + +static int +cmd_wallchan (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + GSList *list; + + if (*word_eol[2]) + { + list = sess_list; + while (list) + { + sess = list->data; + if (sess->type == SESS_CHANNEL) + { + inbound_chanmsg (sess->server, NULL, sess->channel, + sess->server->nick, word_eol[2], TRUE, FALSE); + sess->server->p_message (sess->server, sess->channel, word_eol[2]); + } + list = list->next; + } + return TRUE; + } + return FALSE; +} + +static int +cmd_hop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '+', 'h', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_voice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '+', 'v', 0); + return TRUE; + } + i++; + } +} + +/* *MUST* be kept perfectly sorted for the bsearch to work */ +const struct commands xc_cmds[] = { + {"ADDBUTTON", cmd_addbutton, 0, 0, 1, + N_("ADDBUTTON <name> <action>, adds a button under the user-list")}, + {"ALLCHAN", cmd_allchannels, 0, 0, 1, + N_("ALLCHAN <cmd>, sends a command to all channels you're in")}, + {"ALLCHANL", cmd_allchannelslocal, 0, 0, 1, + N_("ALLCHANL <cmd>, sends a command to all channels you're in")}, + {"ALLSERV", cmd_allservers, 0, 0, 1, + N_("ALLSERV <cmd>, sends a command to all servers you're in")}, + {"AWAY", cmd_away, 1, 0, 1, N_("AWAY [<reason>], sets you away")}, + {"BACK", cmd_back, 1, 0, 1, N_("BACK, sets you back (not away)")}, + {"BAN", cmd_ban, 1, 1, 1, + N_("BAN <mask> [<bantype>], bans everyone matching the mask from the current channel. If they are already on the channel this doesn't kick them (needs chanop)")}, + {"CHANOPT", cmd_chanopt, 0, 0, 1, N_("CHANOPT [-quiet] <variable> [<value>]")}, + {"CHARSET", cmd_charset, 0, 0, 1, 0}, + {"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY], Clears the current text window or command history")}, + {"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE, Closes the current window/tab")}, + + {"COUNTRY", cmd_country, 0, 0, 1, + N_("COUNTRY [-s] <code|wildcard>, finds a country code, eg: au = australia")}, + {"CTCP", cmd_ctcp, 1, 0, 1, + N_("CTCP <nick> <message>, send the CTCP message to nick, common messages are VERSION and USERINFO")}, + {"CYCLE", cmd_cycle, 1, 1, 1, + N_("CYCLE [<channel>], parts the current or given channel and immediately rejoins")}, + {"DCC", cmd_dcc, 0, 0, 1, + N_("\n" + "DCC GET <nick> - accept an offered file\n" + "DCC SEND [-maxcps=#] <nick> [file] - send a file to someone\n" + "DCC PSEND [-maxcps=#] <nick> [file] - send a file using passive mode\n" + "DCC LIST - show DCC list\n" + "DCC CHAT <nick> - offer DCC CHAT to someone\n" + "DCC PCHAT <nick> - offer DCC CHAT using passive mode\n" + "DCC CLOSE <type> <nick> <file> example:\n" + " /dcc close send johnsmith file.tar.gz")}, + {"DEBUG", cmd_debug, 0, 0, 1, 0}, + + {"DEHOP", cmd_dehop, 1, 1, 1, + N_("DEHOP <nick>, removes chanhalf-op status from the nick on the current channel (needs chanop)")}, + {"DELBUTTON", cmd_delbutton, 0, 0, 1, + N_("DELBUTTON <name>, deletes a button from under the user-list")}, + {"DEOP", cmd_deop, 1, 1, 1, + N_("DEOP <nick>, removes chanop status from the nick on the current channel (needs chanop)")}, + {"DEVOICE", cmd_devoice, 1, 1, 1, + N_("DEVOICE <nick>, removes voice status from the nick on the current channel (needs chanop)")}, + {"DISCON", cmd_discon, 0, 0, 1, N_("DISCON, Disconnects from server")}, + {"DNS", cmd_dns, 0, 0, 1, N_("DNS <nick|host|ip>, Finds a users IP number")}, + {"ECHO", cmd_echo, 0, 0, 1, N_("ECHO <text>, Prints text locally")}, +#ifndef WIN32 + {"EXEC", cmd_exec, 0, 0, 1, + N_("EXEC [-o] <command>, runs the command. If -o flag is used then output is sent to current channel, else is printed to current text box")}, +#ifndef __EMX__ + {"EXECCONT", cmd_execc, 0, 0, 1, N_("EXECCONT, sends the process SIGCONT")}, +#endif + {"EXECKILL", cmd_execk, 0, 0, 1, + N_("EXECKILL [-9], kills a running exec in the current session. If -9 is given the process is SIGKILL'ed")}, +#ifndef __EMX__ + {"EXECSTOP", cmd_execs, 0, 0, 1, N_("EXECSTOP, sends the process SIGSTOP")}, + {"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE, sends data to the processes stdin")}, +#endif +#endif + {"FLUSHQ", cmd_flushq, 0, 0, 1, + N_("FLUSHQ, flushes the current server's send queue")}, + {"GATE", cmd_gate, 0, 0, 1, + N_("GATE <host> [<port>], proxies through a host, port defaults to 23")}, + {"GETFILE", cmd_getfile, 0, 0, 1, "GETFILE [-folder] [-multi] [-save] <command> <title> [<initial>]"}, + {"GETINT", cmd_getint, 0, 0, 1, "GETINT <default> <command> <prompt>"}, + {"GETSTR", cmd_getstr, 0, 0, 1, "GETSTR <default> <command> <prompt>"}, + {"GHOST", cmd_ghost, 1, 0, 1, N_("GHOST <nick> [password], Kills a ghosted nickname")}, + {"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY|COLOR <n>]\n" + " GUI [MSGBOX <text>|MENU TOGGLE]"}, + {"HELP", cmd_help, 0, 0, 1, 0}, + {"HOP", cmd_hop, 1, 1, 1, + N_("HOP <nick>, gives chanhalf-op status to the nick (needs chanop)")}, + {"ID", cmd_id, 1, 0, 1, N_("ID <password>, identifies yourself to nickserv")}, + {"IGNORE", cmd_ignore, 0, 0, 1, + N_("IGNORE <mask> <types..> <options..>\n" + " mask - host mask to ignore, eg: *!*@*.aol.com\n" + " types - types of data to ignore, one or all of:\n" + " PRIV, CHAN, NOTI, CTCP, DCC, INVI, ALL\n" + " options - NOSAVE, QUIET")}, + + {"INVITE", cmd_invite, 1, 0, 1, + N_("INVITE <nick> [<channel>], invites someone to a channel, by default the current channel (needs chanop)")}, + {"JOIN", cmd_join, 1, 0, 0, N_("JOIN <channel>, joins the channel")}, + {"KICK", cmd_kick, 1, 1, 1, + N_("KICK <nick>, kicks the nick from the current channel (needs chanop)")}, + {"KICKBAN", cmd_kickban, 1, 1, 1, + N_("KICKBAN <nick>, bans then kicks the nick from the current channel (needs chanop)")}, + {"KILLALL", cmd_killall, 0, 0, 1, "KILLALL, immediately exit"}, + {"LAGCHECK", cmd_lagcheck, 0, 0, 1, + N_("LAGCHECK, forces a new lag check")}, + {"LASTLOG", cmd_lastlog, 0, 0, 1, + N_("LASTLOG <string>, searches for a string in the buffer")}, + {"LIST", cmd_list, 1, 0, 1, 0}, + {"LOAD", cmd_load, 0, 0, 1, N_("LOAD [-e] <file>, loads a plugin or script")}, + + {"MDEHOP", cmd_mdehop, 1, 1, 1, + N_("MDEHOP, Mass deop's all chanhalf-ops in the current channel (needs chanop)")}, + {"MDEOP", cmd_mdeop, 1, 1, 1, + N_("MDEOP, Mass deop's all chanops in the current channel (needs chanop)")}, + {"ME", cmd_me, 0, 0, 1, + N_("ME <action>, sends the action to the current channel (actions are written in the 3rd person, like /me jumps)")}, + {"MENU", cmd_menu, 0, 0, 1, "MENU [-eX] [-i<ICONFILE>] [-k<mod>,<key>] [-m] [-pX] [-r<X,group>] [-tX] {ADD|DEL} <path> [command] [unselect command]\n" + " See http://xchat.org/docs/menu/ for more details."}, + {"MKICK", cmd_mkick, 1, 1, 1, + N_("MKICK, Mass kicks everyone except you in the current channel (needs chanop)")}, + {"MODE", cmd_mode, 1, 0, 1, 0}, + {"MOP", cmd_mop, 1, 1, 1, + N_("MOP, Mass op's all users in the current channel (needs chanop)")}, + {"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message")}, + + {"NAMES", cmd_names, 1, 0, 1, + N_("NAMES, Lists the nicks on the current channel")}, + {"NCTCP", cmd_nctcp, 1, 0, 1, + N_("NCTCP <nick> <message>, Sends a CTCP notice")}, + {"NEWSERVER", cmd_newserver, 0, 0, 1, N_("NEWSERVER [-noconnect] <hostname> [<port>]")}, + {"NICK", cmd_nick, 0, 0, 1, N_("NICK <nickname>, sets your nick")}, + + {"NOTICE", cmd_notice, 1, 0, 1, + N_("NOTICE <nick/channel> <message>, sends a notice. Notices are a type of message that should be auto reacted to")}, + {"NOTIFY", cmd_notify, 0, 0, 1, + N_("NOTIFY [-n network1[,network2,...]] [<nick>], displays your notify list or adds someone to it")}, + {"OP", cmd_op, 1, 1, 1, + N_("OP <nick>, gives chanop status to the nick (needs chanop)")}, + {"PART", cmd_part, 1, 1, 0, + N_("PART [<channel>] [<reason>], leaves the channel, by default the current one")}, + {"PING", cmd_ping, 1, 0, 1, + N_("PING <nick | channel>, CTCP pings nick or channel")}, + {"QUERY", cmd_query, 0, 0, 1, + N_("QUERY [-nofocus] <nick>, opens up a new privmsg window to someone")}, + {"QUIT", cmd_quit, 0, 0, 1, + N_("QUIT [<reason>], disconnects from the current server")}, + {"QUOTE", cmd_quote, 1, 0, 1, + N_("QUOTE <text>, sends the text in raw form to the server")}, +#ifdef USE_OPENSSL + {"RECONNECT", cmd_reconnect, 0, 0, 1, + N_("RECONNECT [-ssl] [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, +#else + {"RECONNECT", cmd_reconnect, 0, 0, 1, + N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, +#endif + {"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to xchat, as if it was received from the irc server")}, + + {"SAY", cmd_say, 0, 0, 1, + N_("SAY <text>, sends the text to the object in the current window")}, + {"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")}, +#ifdef USE_OPENSSL + {"SERVCHAN", cmd_servchan, 0, 0, 1, + N_("SERVCHAN [-ssl] <host> <port> <channel>, connects and joins a channel")}, +#else + {"SERVCHAN", cmd_servchan, 0, 0, 1, + N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")}, +#endif +#ifdef USE_OPENSSL + {"SERVER", cmd_server, 0, 0, 1, + N_("SERVER [-ssl] <host> [<port>] [<password>], connects to a server, the default port is 6667 for normal connections, and 9999 for ssl connections")}, +#else + {"SERVER", cmd_server, 0, 0, 1, + N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")}, +#endif + {"SET", cmd_set, 0, 0, 1, N_("SET [-e] [-off|-on] [-quiet] <variable> [<value>]")}, + {"SETCURSOR", cmd_setcursor, 0, 0, 1, N_("SETCURSOR [-|+]<position>")}, + {"SETTAB", cmd_settab, 0, 0, 1, 0}, + {"SETTEXT", cmd_settext, 0, 0, 1, 0}, + {"SPLAY", cmd_splay, 0, 0, 1, "SPLAY <soundfile>"}, + {"TOPIC", cmd_topic, 1, 1, 1, + N_("TOPIC [<topic>], sets the topic if one is given, else shows the current topic")}, + {"TRAY", cmd_tray, 0, 0, 1, + N_("\nTRAY -f <timeout> <file1> [<file2>] Blink tray between two icons.\n" + "TRAY -f <filename> Set tray to a fixed icon.\n" + "TRAY -i <number> Blink tray with an internal icon.\n" + "TRAY -t <text> Set the tray tooltip.\n" + "TRAY -b <title> <text> Set the tray balloon." + )}, + {"UNBAN", cmd_unban, 1, 1, 1, + N_("UNBAN <mask> [<mask>...], unbans the specified masks.")}, + {"UNIGNORE", cmd_unignore, 0, 0, 1, N_("UNIGNORE <mask> [QUIET]")}, + {"UNLOAD", cmd_unload, 0, 0, 1, N_("UNLOAD <name>, unloads a plugin or script")}, + {"URL", cmd_url, 0, 0, 1, N_("URL <url>, opens a URL in your browser")}, + {"USELECT", cmd_uselect, 0, 1, 0, + N_("USELECT [-a] [-s] <nick1> <nick2> etc, highlights nick(s) in channel userlist")}, + {"USERLIST", cmd_userlist, 1, 1, 1, 0}, + {"VOICE", cmd_voice, 1, 1, 1, + N_("VOICE <nick>, gives voice status to someone (needs chanop)")}, + {"WALLCHAN", cmd_wallchan, 1, 1, 1, + N_("WALLCHAN <message>, writes the message to all channels")}, + {"WALLCHOP", cmd_wallchop, 1, 1, 1, + N_("WALLCHOP <message>, sends the message to all chanops on the current channel")}, + {0, 0, 0, 0, 0, 0} +}; + + +static int +command_compare (const void *a, const void *b) +{ + return strcasecmp (a, ((struct commands *)b)->name); +} + +static struct commands * +find_internal_command (char *name) +{ + /* the "-1" is to skip the NULL terminator */ + return bsearch (name, xc_cmds, (sizeof (xc_cmds) / + sizeof (xc_cmds[0])) - 1, sizeof (xc_cmds[0]), command_compare); +} + +static void +help (session *sess, char *tbuf, char *helpcmd, int quiet) +{ + struct commands *cmd; + + if (plugin_show_help (sess, helpcmd)) + return; + + cmd = find_internal_command (helpcmd); + + if (cmd) + { + if (cmd->help) + { + snprintf (tbuf, TBUFSIZE, _("Usage: %s\n"), _(cmd->help)); + PrintText (sess, tbuf); + } else + { + if (!quiet) + PrintText (sess, _("\nNo help available on that command.\n")); + } + return; + } + + if (!quiet) + PrintText (sess, _("No such command.\n")); +} + +/* inserts %a, %c, %d etc into buffer. Also handles &x %x for word/word_eol. * + * returns 2 on buffer overflow + * returns 1 on success * + * returns 0 on bad-args-for-user-command * + * - word/word_eol args might be NULL * + * - this beast is used for UserCommands, UserlistButtons and CTCP replies */ + +int +auto_insert (char *dest, int destlen, unsigned char *src, char *word[], + char *word_eol[], char *a, char *c, char *d, char *e, char *h, + char *n, char *s) +{ + int num; + char buf[32]; + time_t now; + struct tm *tm_ptr; + char *utf; + gsize utf_len; + char *orig = dest; + + destlen--; + + while (src[0]) + { + if (src[0] == '%' || src[0] == '&') + { + if (isdigit ((unsigned char) src[1])) + { + if (isdigit ((unsigned char) src[2]) && isdigit ((unsigned char) src[3])) + { + buf[0] = src[1]; + buf[1] = src[2]; + buf[2] = src[3]; + buf[3] = 0; + dest[0] = atoi (buf); + utf = g_locale_to_utf8 (dest, 1, 0, &utf_len, 0); + if (utf) + { + if ((dest - orig) + utf_len >= destlen) + { + g_free (utf); + return 2; + } + + memcpy (dest, utf, utf_len); + g_free (utf); + dest += utf_len; + } + src += 3; + } else + { + if (word) + { + src++; + num = src[0] - '0'; /* ascii to decimal */ + if (*word[num] == 0) + return 0; + + if (src[-1] == '%') + utf = word[num]; + else + utf = word_eol[num]; + + /* avoid recusive usercommand overflow */ + if ((dest - orig) + strlen (utf) >= destlen) + return 2; + + strcpy (dest, utf); + dest += strlen (dest); + } + } + } else + { + if (src[0] == '&') + goto lamecode; + src++; + utf = NULL; + switch (src[0]) + { + case '%': + if ((dest - orig) + 2 >= destlen) + return 2; + dest[0] = '%'; + dest[1] = 0; + dest++; + break; + case 'a': + utf = a; break; + case 'c': + utf = c; break; + case 'd': + utf = d; break; + case 'e': + utf = e; break; + case 'h': + utf = h; break; + case 'm': + utf = get_cpu_str (); break; + case 'n': + utf = n; break; + case 's': + utf = s; break; + case 't': + now = time (0); + utf = ctime (&now); + utf[19] = 0; + break; + case 'v': + utf = PACKAGE_VERSION; break; + break; + case 'y': + now = time (0); + tm_ptr = localtime (&now); + snprintf (buf, sizeof (buf), "%4d%02d%02d", 1900 + + tm_ptr->tm_year, 1 + tm_ptr->tm_mon, tm_ptr->tm_mday); + utf = buf; + break; + default: + src--; + goto lamecode; + } + + if (utf) + { + if ((dest - orig) + strlen (utf) >= destlen) + return 2; + strcpy (dest, utf); + dest += strlen (dest); + } + + } + src++; + } else + { + utf_len = g_utf8_skip[src[0]]; + + if ((dest - orig) + utf_len >= destlen) + return 2; + + if (utf_len == 1) + { + lamecode: + dest[0] = src[0]; + dest++; + src++; + } else + { + memcpy (dest, src, utf_len); + dest += utf_len; + src += utf_len; + } + } + } + + dest[0] = 0; + + return 1; +} + +void +check_special_chars (char *cmd, int do_ascii) /* check for %X */ +{ + int occur = 0; + int len = strlen (cmd); + char *buf, *utf; + char tbuf[4]; + int i = 0, j = 0; + gsize utf_len; + + if (!len) + return; + + buf = malloc (len + 1); + + if (buf) + { + while (cmd[j]) + { + switch (cmd[j]) + { + case '%': + occur++; + if ( do_ascii && + j + 3 < len && + (isdigit ((unsigned char) cmd[j + 1]) && isdigit ((unsigned char) cmd[j + 2]) && + isdigit ((unsigned char) cmd[j + 3]))) + { + tbuf[0] = cmd[j + 1]; + tbuf[1] = cmd[j + 2]; + tbuf[2] = cmd[j + 3]; + tbuf[3] = 0; + buf[i] = atoi (tbuf); + utf = g_locale_to_utf8 (buf + i, 1, 0, &utf_len, 0); + if (utf) + { + memcpy (buf + i, utf, utf_len); + g_free (utf); + i += (utf_len - 1); + } + j += 3; + } else + { + switch (cmd[j + 1]) + { + case 'R': + buf[i] = '\026'; + break; + case 'U': + buf[i] = '\037'; + break; + case 'B': + buf[i] = '\002'; + break; + case 'C': + buf[i] = '\003'; + break; + case 'O': + buf[i] = '\017'; + break; + case 'H': /* CL: invisible text code */ + buf[i] = HIDDEN_CHAR; + break; + case '%': + buf[i] = '%'; + break; + default: + buf[i] = '%'; + j--; + break; + } + j++; + break; + default: + buf[i] = cmd[j]; + } + } + j++; + i++; + } + buf[i] = 0; + if (occur) + strcpy (cmd, buf); + free (buf); + } +} + +typedef struct +{ + char *nick; + int len; + struct User *best; + int bestlen; + char *space; + char *tbuf; +} nickdata; + +static int +nick_comp_cb (struct User *user, nickdata *data) +{ + int lenu; + + if (!rfc_ncasecmp (user->nick, data->nick, data->len)) + { + lenu = strlen (user->nick); + if (lenu == data->len) + { + snprintf (data->tbuf, TBUFSIZE, "%s%s", user->nick, data->space); + data->len = -1; + return FALSE; + } else if (lenu < data->bestlen) + { + data->bestlen = lenu; + data->best = user; + } + } + + return TRUE; +} + +static void +perform_nick_completion (struct session *sess, char *cmd, char *tbuf) +{ + int len; + char *space = strchr (cmd, ' '); + if (space && space != cmd) + { + if (space[-1] == prefs.nick_suffix[0] && space - 1 != cmd) + { + len = space - cmd - 1; + if (len < NICKLEN) + { + char nick[NICKLEN]; + nickdata data; + + memcpy (nick, cmd, len); + nick[len] = 0; + + data.nick = nick; + data.len = len; + data.bestlen = INT_MAX; + data.best = NULL; + data.tbuf = tbuf; + data.space = space - 1; + tree_foreach (sess->usertree, (tree_traverse_func *)nick_comp_cb, &data); + + if (data.len == -1) + return; + + if (data.best) + { + snprintf (tbuf, TBUFSIZE, "%s%s", data.best->nick, space - 1); + return; + } + } + } + } + + strcpy (tbuf, cmd); +} + +static void +user_command (session * sess, char *tbuf, char *cmd, char *word[], + char *word_eol[]) +{ + if (!auto_insert (tbuf, 2048, cmd, word, word_eol, "", sess->channel, "", + server_get_network (sess->server, TRUE), "", + sess->server->nick, "")) + { + PrintText (sess, _("Bad arguments for user command.\n")); + return; + } + + handle_command (sess, tbuf, TRUE); +} + +/* handle text entered without a CMDchar prefix */ + +static void +handle_say (session *sess, char *text, int check_spch) +{ + struct DCC *dcc; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + char pdibuf_static[1024]; + char newcmd_static[1024]; + char *pdibuf = pdibuf_static; + char *newcmd = newcmd_static; + int len; + int newcmdlen = sizeof newcmd_static; + + if (strcmp (sess->channel, "(lastlog)") == 0) + { + lastlog (sess->lastlog_sess, text, sess->lastlog_regexp); + return; + } + + len = strlen (text); + if (len >= sizeof pdibuf_static) + pdibuf = malloc (len + 1); + + if (len + NICKLEN >= newcmdlen) + newcmd = malloc (newcmdlen = len + NICKLEN + 1); + + if (check_spch && prefs.perc_color) + check_special_chars (text, prefs.perc_ascii); + + /* Python relies on this */ + word[PDIWORDS] = NULL; + word_eol[PDIWORDS] = NULL; + + /* split the text into words and word_eol */ + process_data_init (pdibuf, text, word, word_eol, TRUE, FALSE); + + /* a command of "" can be hooked for non-commands */ + if (plugin_emit_command (sess, "", word, word_eol)) + goto xit; + + /* incase a plugin did /close */ + if (!is_session (sess)) + goto xit; + + if (!sess->channel[0] || sess->type == SESS_SERVER || sess->type == SESS_NOTICES || sess->type == SESS_SNOTICES) + { + notj_msg (sess); + goto xit; + } + + if (prefs.nickcompletion) + perform_nick_completion (sess, text, newcmd); + else + safe_strcpy (newcmd, text, newcmdlen); + + text = newcmd; + + if (sess->type == SESS_DIALOG) + { + /* try it via dcc, if possible */ + dcc = dcc_write_chat (sess->channel, text); + if (dcc) + { + inbound_chanmsg (sess->server, NULL, sess->channel, + sess->server->nick, text, TRUE, FALSE); + set_topic (sess, net_ip (dcc->addr), net_ip (dcc->addr)); + goto xit; + } + } + + if (sess->server->connected) + { + unsigned int max; + unsigned char t = 0; + + /* maximum allowed message text */ + /* :nickname!username@host.com PRIVMSG #channel :text\r\n */ + max = 512; + max -= 16; /* :, !, @, " PRIVMSG ", " ", :, \r, \n */ + max -= strlen (sess->server->nick); + max -= strlen (sess->channel); + if (sess->me && sess->me->hostname) + max -= strlen (sess->me->hostname); + else + { + max -= 9; /* username */ + max -= 65; /* max possible hostname and '@' */ + } + + if (strlen (text) > max) + { + int i = 0, size; + + /* traverse the utf8 string and find the nearest cut point that + doesn't split 1 char in half */ + while (1) + { + size = g_utf8_skip[((unsigned char *)text)[i]]; + if ((i + size) >= max) + break; + i += size; + } + max = i; + t = text[max]; + text[max] = 0; /* insert a NULL terminator to shorten it */ + } + + inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick, + text, TRUE, FALSE); + sess->server->p_message (sess->server, sess->channel, text); + + if (t) + { + text[max] = t; + handle_say (sess, text + max, FALSE); + } + + } else + { + notc_msg (sess); + } + +xit: + if (pdibuf != pdibuf_static) + free (pdibuf); + + if (newcmd != newcmd_static) + free (newcmd); +} + +/* handle a command, without the '/' prefix */ + +int +handle_command (session *sess, char *cmd, int check_spch) +{ + struct popup *pop; + int user_cmd = FALSE; + GSList *list; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + static int command_level = 0; + struct commands *int_cmd; + char pdibuf_static[1024]; + char tbuf_static[TBUFSIZE]; + char *pdibuf; + char *tbuf; + int len; + int ret = TRUE; + + if (command_level > 99) + { + fe_message (_("Too many recursive usercommands, aborting."), FE_MSG_ERROR); + return TRUE; + } + command_level++; + /* anything below MUST DEC command_level before returning */ + + len = strlen (cmd); + if (len >= sizeof (pdibuf_static)) + pdibuf = malloc (len + 1); + else + pdibuf = pdibuf_static; + + if ((len * 2) >= sizeof (tbuf_static)) + tbuf = malloc ((len * 2) + 1); + else + tbuf = tbuf_static; + + /* split the text into words and word_eol */ + process_data_init (pdibuf, cmd, word, word_eol, TRUE, TRUE); + + /* ensure an empty string at index 32 for cmd_deop etc */ + /* (internal use only, plugins can still only read 1-31). */ + word[PDIWORDS] = "\000\000"; + word_eol[PDIWORDS] = "\000\000"; + + int_cmd = find_internal_command (word[1]); + /* redo it without quotes processing, for some commands like /JOIN */ + if (int_cmd && !int_cmd->handle_quotes) + process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE); + + if (check_spch && prefs.perc_color) + check_special_chars (cmd, prefs.perc_ascii); + + if (plugin_emit_command (sess, word[1], word, word_eol)) + goto xit; + + /* incase a plugin did /close */ + if (!is_session (sess)) + goto xit; + + /* first see if it's a userCommand */ + list = command_list; + while (list) + { + pop = (struct popup *) list->data; + if (!strcasecmp (pop->name, word[1])) + { + user_command (sess, tbuf, pop->cmd, word, word_eol); + user_cmd = TRUE; + } + list = list->next; + } + + if (user_cmd) + goto xit; + + /* now check internal commands */ + int_cmd = find_internal_command (word[1]); + + if (int_cmd) + { + if (int_cmd->needserver && !sess->server->connected) + { + notc_msg (sess); + } else if (int_cmd->needchannel && !sess->channel[0]) + { + notj_msg (sess); + } else + { + switch (int_cmd->callback (sess, tbuf, word, word_eol)) + { + case FALSE: + help (sess, tbuf, int_cmd->name, TRUE); + break; + case 2: + ret = FALSE; + goto xit; + } + } + } else + { + /* unknown command, just send it to the server and hope */ + if (!sess->server->connected) + PrintText (sess, _("Unknown Command. Try /help\n")); + else + sess->server->p_raw (sess->server, cmd); + } + +xit: + command_level--; + + if (pdibuf != pdibuf_static) + free (pdibuf); + + if (tbuf != tbuf_static) + free (tbuf); + + return ret; +} + +/* handle one line entered into the input box */ + +static int +handle_user_input (session *sess, char *text, int history, int nocommand) +{ + if (*text == '\0') + return 1; + + if (history) + history_add (&sess->history, text); + + /* is it NOT a command, just text? */ + if (nocommand || text[0] != prefs.cmdchar[0]) + { + handle_say (sess, text, TRUE); + return 1; + } + + /* check for // */ + if (text[0] == prefs.cmdchar[0] && text[1] == prefs.cmdchar[0]) + { + handle_say (sess, text + 1, TRUE); + return 1; + } + + if (prefs.cmdchar[0] == '/') + { + int i; + const char *unix_dirs [] = { + "/bin/", "/boot/", "/dev/", + "/etc/", "/home/", "/lib/", + "/lost+found/", "/mnt/", "/opt/", + "/proc/", "/root/", "/sbin/", + "/tmp/", "/usr/", "/var/", + "/gnome/", NULL}; + for (i = 0; unix_dirs[i] != NULL; i++) + if (strncmp (text, unix_dirs[i], strlen (unix_dirs[i]))==0) + { + handle_say (sess, text, TRUE); + return 1; + } + } + + return handle_command (sess, text + 1, TRUE); +} + +/* changed by Steve Green. Macs sometimes paste with imbedded \r */ +void +handle_multiline (session *sess, char *cmd, int history, int nocommand) +{ + while (*cmd) + { + char *cr = cmd + strcspn (cmd, "\n\r"); + int end_of_string = *cr == 0; + *cr = 0; + if (!handle_user_input (sess, cmd, history, nocommand)) + return; + if (end_of_string) + break; + cmd = cr + 1; + } +} + +/*void +handle_multiline (session *sess, char *cmd, int history, int nocommand) +{ + char *cr; + + cr = strchr (cmd, '\n'); + if (cr) + { + while (1) + { + if (cr) + *cr = 0; + if (!handle_user_input (sess, cmd, history, nocommand)) + return; + if (!cr) + break; + cmd = cr + 1; + if (*cmd == 0) + break; + cr = strchr (cmd, '\n'); + } + } else + { + handle_user_input (sess, cmd, history, nocommand); + } +}*/ diff --git a/src/common/outbound.h b/src/common/outbound.h new file mode 100644 index 00000000..a2047845 --- /dev/null +++ b/src/common/outbound.h @@ -0,0 +1,20 @@ +#ifndef XCHAT_OUTBOUND_H +#define XCHAT_OUTBOUND_H + +extern const struct commands xc_cmds[]; +extern GSList *menu_list; + +int auto_insert (char *dest, int destlen, unsigned char *src, char *word[], char *word_eol[], + char *a, char *c, char *d, char *e, char *h, char *n, char *s); +int handle_command (session *sess, char *cmd, int check_spch); +void process_data_init (char *buf, char *cmd, char *word[], char *word_eol[], gboolean handle_quotes, gboolean allow_escape_quotes); +void handle_multiline (session *sess, char *cmd, int history, int nocommand); +void check_special_chars (char *cmd, int do_ascii); +void notc_msg (session *sess); +void server_sendpart (server * serv, char *channel, char *reason); +void server_sendquit (session * sess); +int menu_streq (const char *s1, const char *s2, int def); +void open_query (server *serv, char *nick, gboolean focus_existing); +gboolean load_perform_file (session *sess, char *file); + +#endif diff --git a/src/common/plugin-timer.c b/src/common/plugin-timer.c new file mode 100644 index 00000000..f09074a8 --- /dev/null +++ b/src/common/plugin-timer.c @@ -0,0 +1,208 @@ +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include "xchat-plugin.h" + +#ifdef WIN32 +#define strcasecmp stricmp +#endif + +static xchat_plugin *ph; /* plugin handle */ +static GSList *timer_list = NULL; + +#define STATIC +#define HELP \ +"Usage: TIMER [-refnum <num>] [-repeat <num>] <seconds> <command>\n" \ +" TIMER [-quiet] -delete <num>" + +typedef struct +{ + xchat_hook *hook; + xchat_context *context; + char *command; + int ref; + int repeat; + float timeout; + unsigned int forever:1; +} timer; + +static void +timer_del (timer *tim) +{ + timer_list = g_slist_remove (timer_list, tim); + free (tim->command); + xchat_unhook (ph, tim->hook); + free (tim); +} + +static void +timer_del_ref (int ref, int quiet) +{ + GSList *list; + timer *tim; + + list = timer_list; + while (list) + { + tim = list->data; + if (tim->ref == ref) + { + timer_del (tim); + if (!quiet) + xchat_printf (ph, "Timer %d deleted.\n", ref); + return; + } + list = list->next; + } + if (!quiet) + xchat_print (ph, "No such ref number found.\n"); +} + +static int +timeout_cb (timer *tim) +{ + if (xchat_set_context (ph, tim->context)) + { + xchat_command (ph, tim->command); + + if (tim->forever) + return 1; + + tim->repeat--; + if (tim->repeat > 0) + return 1; + } + + timer_del (tim); + return 0; +} + +static void +timer_add (int ref, float timeout, int repeat, char *command) +{ + timer *tim; + GSList *list; + + if (ref == 0) + { + ref = 1; + list = timer_list; + while (list) + { + tim = list->data; + if (tim->ref >= ref) + ref = tim->ref + 1; + list = list->next; + } + } + + tim = malloc (sizeof (timer)); + tim->ref = ref; + tim->repeat = repeat; + tim->timeout = timeout; + tim->command = strdup (command); + tim->context = xchat_get_context (ph); + tim->forever = FALSE; + + if (repeat == 0) + tim->forever = TRUE; + + tim->hook = xchat_hook_timer (ph, timeout * 1000.0, (void *)timeout_cb, tim); + timer_list = g_slist_append (timer_list, tim); +} + +static void +timer_showlist (void) +{ + GSList *list; + timer *tim; + + if (timer_list == NULL) + { + xchat_print (ph, "No timers installed.\n"); + xchat_print (ph, HELP); + return; + } + /* 00000 00000000 0000000 abc */ + xchat_print (ph, "\026 Ref# Seconds Repeat Command \026\n"); + list = timer_list; + while (list) + { + tim = list->data; + xchat_printf (ph, "%5d %8.1f %7d %s\n", tim->ref, tim->timeout, + tim->repeat, tim->command); + list = list->next; + } +} + +static int +timer_cb (char *word[], char *word_eol[], void *userdata) +{ + int repeat = 1; + float timeout; + int offset = 0; + int ref = 0; + int quiet = FALSE; + char *command; + + if (!word[2][0]) + { + timer_showlist (); + return XCHAT_EAT_XCHAT; + } + + if (strcasecmp (word[2], "-quiet") == 0) + { + quiet = TRUE; + offset++; + } + + if (strcasecmp (word[2 + offset], "-delete") == 0) + { + timer_del_ref (atoi (word[3 + offset]), quiet); + return XCHAT_EAT_XCHAT; + } + + if (strcasecmp (word[2 + offset], "-refnum") == 0) + { + ref = atoi (word[3 + offset]); + offset += 2; + } + + if (strcasecmp (word[2 + offset], "-repeat") == 0) + { + repeat = atoi (word[3 + offset]); + offset += 2; + } + + timeout = atof (word[2 + offset]); + command = word_eol[3 + offset]; + + if (timeout < 0.1 || !command[0]) + xchat_print (ph, HELP); + else + timer_add (ref, timeout, repeat, command); + + return XCHAT_EAT_XCHAT; +} + +int +#ifdef STATIC +timer_plugin_init +#else +xchat_plugin_init +#endif + (xchat_plugin *plugin_handle, char **plugin_name, + char **plugin_desc, char **plugin_version, char *arg) +{ + /* we need to save this for use with any xchat_* functions */ + ph = plugin_handle; + + *plugin_name = "Timer"; + *plugin_desc = "IrcII style /TIMER command"; + *plugin_version = ""; + + xchat_hook_command (ph, "TIMER", XCHAT_PRI_NORM, timer_cb, HELP, 0); + + return 1; /* return 1 for success */ +} diff --git a/src/common/plugin-timer.h b/src/common/plugin-timer.h new file mode 100644 index 00000000..e3530c8d --- /dev/null +++ b/src/common/plugin-timer.h @@ -0,0 +1,7 @@ +#ifndef XCHAT_PLUGIN_TIMER_H +#define XCHAT_PLUGIN_TIMER_H + +int timer_plugin_init (xchat_plugin *plugin_handle, char **plugin_name, + char **plugin_desc, char **plugin_version, char *arg); + +#endif diff --git a/src/common/plugin.c b/src/common/plugin.c new file mode 100644 index 00000000..ada4d3be --- /dev/null +++ b/src/common/plugin.c @@ -0,0 +1,1569 @@ +/* X-Chat + * Copyright (C) 2002 Peter Zelezny. + * + * 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 <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> + +#include "xchat.h" +#include "fe.h" +#include "util.h" +#include "outbound.h" +#include "cfgfiles.h" +#include "ignore.h" +#include "server.h" +#include "servlist.h" +#include "modes.h" +#include "notify.h" +#include "text.h" +#define PLUGIN_C +typedef struct session xchat_context; +#include "xchat-plugin.h" +#include "plugin.h" + + +#include "xchatc.h" + +/* the USE_PLUGIN define only removes libdl stuff */ + +#ifdef USE_PLUGIN +#ifdef USE_GMODULE +#include <gmodule.h> +#else +#include <dlfcn.h> +#endif +#endif + +#define DEBUG(x) {x;} + +/* crafted to be an even 32 bytes */ +struct _xchat_hook +{ + xchat_plugin *pl; /* the plugin to which it belongs */ + char *name; /* "xdcc" */ + void *callback; /* pointer to xdcc_callback */ + char *help_text; /* help_text for commands only */ + void *userdata; /* passed to the callback */ + int tag; /* for timers & FDs only */ + int type; /* HOOK_* */ + int pri; /* fd */ /* priority / fd for HOOK_FD only */ +}; + +struct _xchat_list +{ + int type; /* LIST_* */ + GSList *pos; /* current pos */ + GSList *next; /* next pos */ + GSList *head; /* for LIST_USERS only */ + struct notify_per_server *notifyps; /* notify_per_server * */ +}; + +typedef int (xchat_cmd_cb) (char *word[], char *word_eol[], void *user_data); +typedef int (xchat_serv_cb) (char *word[], char *word_eol[], void *user_data); +typedef int (xchat_print_cb) (char *word[], void *user_data); +typedef int (xchat_fd_cb) (int fd, int flags, void *user_data); +typedef int (xchat_timer_cb) (void *user_data); +typedef int (xchat_init_func) (xchat_plugin *, char **, char **, char **, char *); +typedef int (xchat_deinit_func) (xchat_plugin *); + +enum +{ + LIST_CHANNELS, + LIST_DCC, + LIST_IGNORE, + LIST_NOTIFY, + LIST_USERS +}; + +enum +{ + HOOK_COMMAND, /* /command */ + HOOK_SERVER, /* PRIVMSG, NOTICE, numerics */ + HOOK_PRINT, /* All print events */ + HOOK_TIMER, /* timeouts */ + HOOK_FD, /* sockets & fds */ + HOOK_DELETED /* marked for deletion */ +}; + +GSList *plugin_list = NULL; /* export for plugingui.c */ +static GSList *hook_list = NULL; + +extern const struct prefs vars[]; /* cfgfiles.c */ + + +/* unload a plugin and remove it from our linked list */ + +static int +plugin_free (xchat_plugin *pl, int do_deinit, int allow_refuse) +{ + GSList *list, *next; + xchat_hook *hook; + xchat_deinit_func *deinit_func; + + /* fake plugin added by xchat_plugingui_add() */ + if (pl->fake) + goto xit; + + /* run the plugin's deinit routine, if any */ + if (do_deinit && pl->deinit_callback != NULL) + { + deinit_func = pl->deinit_callback; + if (!deinit_func (pl) && allow_refuse) + return FALSE; + } + + /* remove all of this plugin's hooks */ + list = hook_list; + while (list) + { + hook = list->data; + next = list->next; + if (hook->pl == pl) + xchat_unhook (NULL, hook); + list = next; + } + +#ifdef USE_PLUGIN + if (pl->handle) +#ifdef USE_GMODULE + g_module_close (pl->handle); +#else + dlclose (pl->handle); +#endif +#endif + +xit: + if (pl->free_strings) + { + if (pl->name) + free (pl->name); + if (pl->desc) + free (pl->desc); + if (pl->version) + free (pl->version); + } + if (pl->filename) + free ((char *)pl->filename); + free (pl); + + plugin_list = g_slist_remove (plugin_list, pl); + +#ifdef USE_PLUGIN + fe_pluginlist_update (); +#endif + + return TRUE; +} + +static xchat_plugin * +plugin_list_add (xchat_context *ctx, char *filename, const char *name, + const char *desc, const char *version, void *handle, + void *deinit_func, int fake, int free_strings) +{ + xchat_plugin *pl; + + pl = malloc (sizeof (xchat_plugin)); + pl->handle = handle; + pl->filename = filename; + pl->context = ctx; + pl->name = (char *)name; + pl->desc = (char *)desc; + pl->version = (char *)version; + pl->deinit_callback = deinit_func; + pl->fake = fake; + pl->free_strings = free_strings; /* free() name,desc,version? */ + + plugin_list = g_slist_prepend (plugin_list, pl); + + return pl; +} + +static void * +xchat_dummy (xchat_plugin *ph) +{ + return NULL; +} + +#ifdef WIN32 +static int +xchat_read_fd (xchat_plugin *ph, GIOChannel *source, char *buf, int *len) +{ + return g_io_channel_read (source, buf, *len, len); +} +#endif + +/* Load a static plugin */ + +void +plugin_add (session *sess, char *filename, void *handle, void *init_func, + void *deinit_func, char *arg, int fake) +{ + xchat_plugin *pl; + char *file; + + file = NULL; + if (filename) + file = strdup (filename); + + pl = plugin_list_add (sess, file, file, NULL, NULL, handle, deinit_func, + fake, FALSE); + + if (!fake) + { + /* win32 uses these because it doesn't have --export-dynamic! */ + pl->xchat_hook_command = xchat_hook_command; + pl->xchat_hook_server = xchat_hook_server; + pl->xchat_hook_print = xchat_hook_print; + pl->xchat_hook_timer = xchat_hook_timer; + pl->xchat_hook_fd = xchat_hook_fd; + pl->xchat_unhook = xchat_unhook; + pl->xchat_print = xchat_print; + pl->xchat_printf = xchat_printf; + pl->xchat_command = xchat_command; + pl->xchat_commandf = xchat_commandf; + pl->xchat_nickcmp = xchat_nickcmp; + pl->xchat_set_context = xchat_set_context; + pl->xchat_find_context = xchat_find_context; + pl->xchat_get_context = xchat_get_context; + pl->xchat_get_info = xchat_get_info; + pl->xchat_get_prefs = xchat_get_prefs; + pl->xchat_list_get = xchat_list_get; + pl->xchat_list_free = xchat_list_free; + pl->xchat_list_fields = xchat_list_fields; + pl->xchat_list_str = xchat_list_str; + pl->xchat_list_next = xchat_list_next; + pl->xchat_list_int = xchat_list_int; + pl->xchat_plugingui_add = xchat_plugingui_add; + pl->xchat_plugingui_remove = xchat_plugingui_remove; + pl->xchat_emit_print = xchat_emit_print; +#ifdef WIN32 + pl->xchat_read_fd = (void *) xchat_read_fd; +#else + pl->xchat_read_fd = xchat_dummy; +#endif + pl->xchat_list_time = xchat_list_time; + pl->xchat_gettext = xchat_gettext; + pl->xchat_send_modes = xchat_send_modes; + pl->xchat_strip = xchat_strip; + pl->xchat_free = xchat_free; + + /* incase new plugins are loaded on older xchat */ + pl->xchat_dummy4 = xchat_dummy; + pl->xchat_dummy3 = xchat_dummy; + pl->xchat_dummy2 = xchat_dummy; + pl->xchat_dummy1 = xchat_dummy; + + /* run xchat_plugin_init, if it returns 0, close the plugin */ + if (((xchat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0) + { + plugin_free (pl, FALSE, FALSE); + return; + } + } + +#ifdef USE_PLUGIN + fe_pluginlist_update (); +#endif +} + +/* kill any plugin by the given (file) name (used by /unload) */ + +int +plugin_kill (char *name, int by_filename) +{ + GSList *list; + xchat_plugin *pl; + + list = plugin_list; + while (list) + { + pl = list->data; + /* static-plugins (plugin-timer.c) have a NULL filename */ + if ((by_filename && pl->filename && strcasecmp (name, pl->filename) == 0) || + (by_filename && pl->filename && strcasecmp (name, file_part (pl->filename)) == 0) || + (!by_filename && strcasecmp (name, pl->name) == 0)) + { + /* statically linked plugins have a NULL filename */ + if (pl->filename != NULL && !pl->fake) + { + if (plugin_free (pl, TRUE, TRUE)) + return 1; + return 2; + } + } + list = list->next; + } + + return 0; +} + +/* kill all running plugins (at shutdown) */ + +void +plugin_kill_all (void) +{ + GSList *list, *next; + xchat_plugin *pl; + + list = plugin_list; + while (list) + { + pl = list->data; + next = list->next; + if (!pl->fake) + plugin_free (list->data, TRUE, FALSE); + list = next; + } +} + +#ifdef USE_PLUGIN + +/* load a plugin from a filename. Returns: NULL-success or an error string */ + +char * +plugin_load (session *sess, char *filename, char *arg) +{ + void *handle; + xchat_init_func *init_func; + xchat_deinit_func *deinit_func; + +#ifdef USE_GMODULE + /* load the plugin */ + handle = g_module_open (filename, 0); + if (handle == NULL) + return (char *)g_module_error (); + + /* find the init routine xchat_plugin_init */ + if (!g_module_symbol (handle, "xchat_plugin_init", (gpointer *)&init_func)) + { + g_module_close (handle); + return _("No xchat_plugin_init symbol; is this really an xchat plugin?"); + } + + /* find the plugin's deinit routine, if any */ + if (!g_module_symbol (handle, "xchat_plugin_deinit", (gpointer *)&deinit_func)) + deinit_func = NULL; + +#else + char *error; + char *filepart; + +/* OpenBSD lacks this! */ +#ifndef RTLD_GLOBAL +#define RTLD_GLOBAL 0 +#endif + +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif + + /* get the filename without path */ + filepart = file_part (filename); + + /* load the plugin */ + if (filepart && + /* xsys draws in libgtk-1.2, causing crashes, so force RTLD_LOCAL */ + (strstr (filepart, "local") || strncmp (filepart, "libxsys-1", 9) == 0) + ) + handle = dlopen (filename, RTLD_NOW); + else + handle = dlopen (filename, RTLD_GLOBAL | RTLD_NOW); + if (handle == NULL) + return (char *)dlerror (); + dlerror (); /* Clear any existing error */ + + /* find the init routine xchat_plugin_init */ + init_func = dlsym (handle, "xchat_plugin_init"); + error = (char *)dlerror (); + if (error != NULL) + { + dlclose (handle); + return _("No xchat_plugin_init symbol; is this really an xchat plugin?"); + } + + /* find the plugin's deinit routine, if any */ + deinit_func = dlsym (handle, "xchat_plugin_deinit"); + error = (char *)dlerror (); +#endif + + /* add it to our linked list */ + plugin_add (sess, filename, handle, init_func, deinit_func, arg, FALSE); + + return NULL; +} + +static session *ps; + +static void +plugin_auto_load_cb (char *filename) +{ + char *pMsg; + +#ifndef WIN32 /* black listed */ + if (!strcmp (file_part (filename), "dbus.so")) + return; +#endif + + pMsg = plugin_load (ps, filename, NULL); + if (pMsg) + { + PrintTextf (ps, "AutoLoad failed for: %s\n", filename); + PrintText (ps, pMsg); + } +} + +void +plugin_auto_load (session *sess) +{ + ps = sess; +#ifdef WIN32 + for_files ("./plugins", "*.dll", plugin_auto_load_cb); + for_files (get_xdir_fs (), "*.dll", plugin_auto_load_cb); +#else +#if defined(__hpux) + for_files (XCHATLIBDIR"/plugins", "*.sl", plugin_auto_load_cb); + for_files (get_xdir_fs (), "*.sl", plugin_auto_load_cb); +#else + for_files (XCHATLIBDIR"/plugins", "*.so", plugin_auto_load_cb); + for_files (get_xdir_fs (), "*.so", plugin_auto_load_cb); +#endif +#endif +} + +#endif + +static GSList * +plugin_hook_find (GSList *list, int type, char *name) +{ + xchat_hook *hook; + + while (list) + { + hook = list->data; + if (hook->type == type) + { + if (strcasecmp (hook->name, name) == 0) + return list; + + if (type == HOOK_SERVER) + { + if (strcasecmp (hook->name, "RAW LINE") == 0) + return list; + } + } + list = list->next; + } + + return NULL; +} + +/* check for plugin hooks and run them */ + +static int +plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], int type) +{ + GSList *list, *next; + xchat_hook *hook; + int ret, eat = 0; + + list = hook_list; + while (1) + { + list = plugin_hook_find (list, type, name); + if (!list) + goto xit; + + hook = list->data; + next = list->next; + hook->pl->context = sess; + + /* run the plugin's callback function */ + switch (type) + { + case HOOK_COMMAND: + ret = ((xchat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata); + break; + case HOOK_SERVER: + ret = ((xchat_serv_cb *)hook->callback) (word, word_eol, hook->userdata); + break; + default: /*case HOOK_PRINT:*/ + ret = ((xchat_print_cb *)hook->callback) (word, hook->userdata); + break; + } + + if ((ret & XCHAT_EAT_XCHAT) && (ret & XCHAT_EAT_PLUGIN)) + { + eat = 1; + goto xit; + } + if (ret & XCHAT_EAT_PLUGIN) + goto xit; /* stop running plugins */ + if (ret & XCHAT_EAT_XCHAT) + eat = 1; /* eventually we'll return 1, but continue running plugins */ + + list = next; + } + +xit: + /* really remove deleted hooks now */ + list = hook_list; + while (list) + { + hook = list->data; + next = list->next; + if (hook->type == HOOK_DELETED) + { + hook_list = g_slist_remove (hook_list, hook); + free (hook); + } + list = next; + } + + return eat; +} + +/* execute a plugged in command. Called from outbound.c */ + +int +plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]) +{ + return plugin_hook_run (sess, name, word, word_eol, HOOK_COMMAND); +} + +/* got a server PRIVMSG, NOTICE, numeric etc... */ + +int +plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[]) +{ + return plugin_hook_run (sess, name, word, word_eol, HOOK_SERVER); +} + +/* see if any plugins are interested in this print event */ + +int +plugin_emit_print (session *sess, char *word[]) +{ + return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT); +} + +int +plugin_emit_dummy_print (session *sess, char *name) +{ + char *word[32]; + int i; + + word[0] = name; + for (i = 1; i < 32; i++) + word[i] = "\000"; + + return plugin_hook_run (sess, name, word, NULL, HOOK_PRINT); +} + +int +plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, + int len, char *string) +{ + char *word[PDIWORDS]; + char keyval_str[16]; + char state_str[16]; + char len_str[16]; + int i; + + if (!hook_list) + return 0; + + sprintf (keyval_str, "%u", keyval); + sprintf (state_str, "%u", state); + sprintf (len_str, "%d", len); + + word[0] = "Key Press"; + word[1] = keyval_str; + word[2] = state_str; + word[3] = string; + word[4] = len_str; + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT); +} + +static int +plugin_timeout_cb (xchat_hook *hook) +{ + int ret; + + /* timer_cb's context starts as front-most-tab */ + hook->pl->context = current_sess; + + /* call the plugin's timeout function */ + ret = ((xchat_timer_cb *)hook->callback) (hook->userdata); + + /* the callback might have already unhooked it! */ + if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED) + return 0; + + if (ret == 0) + { + hook->tag = 0; /* avoid fe_timeout_remove, returning 0 is enough! */ + xchat_unhook (hook->pl, hook); + } + + return ret; +} + +/* insert a hook into hook_list according to its priority */ + +static void +plugin_insert_hook (xchat_hook *new_hook) +{ + GSList *list; + xchat_hook *hook; + + list = hook_list; + while (list) + { + hook = list->data; + if (hook->type == new_hook->type && hook->pri <= new_hook->pri) + { + hook_list = g_slist_insert_before (hook_list, list, new_hook); + return; + } + list = list->next; + } + + hook_list = g_slist_append (hook_list, new_hook); +} + +static gboolean +plugin_fd_cb (GIOChannel *source, GIOCondition condition, xchat_hook *hook) +{ + int flags = 0, ret; + typedef int (xchat_fd_cb2) (int fd, int flags, void *user_data, GIOChannel *); + + if (condition & G_IO_IN) + flags |= XCHAT_FD_READ; + if (condition & G_IO_OUT) + flags |= XCHAT_FD_WRITE; + if (condition & G_IO_PRI) + flags |= XCHAT_FD_EXCEPTION; + + ret = ((xchat_fd_cb2 *)hook->callback) (hook->pri, flags, hook->userdata, source); + + /* the callback might have already unhooked it! */ + if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED) + return 0; + + if (ret == 0) + { + hook->tag = 0; /* avoid fe_input_remove, returning 0 is enough! */ + xchat_unhook (hook->pl, hook); + } + + return ret; +} + +/* allocate and add a hook to our list. Used for all 4 types */ + +static xchat_hook * +plugin_add_hook (xchat_plugin *pl, int type, int pri, const char *name, + const char *help_text, void *callb, int timeout, void *userdata) +{ + xchat_hook *hook; + + hook = malloc (sizeof (xchat_hook)); + memset (hook, 0, sizeof (xchat_hook)); + + hook->type = type; + hook->pri = pri; + if (name) + hook->name = strdup (name); + if (help_text) + hook->help_text = strdup (help_text); + hook->callback = callb; + hook->pl = pl; + hook->userdata = userdata; + + /* insert it into the linked list */ + plugin_insert_hook (hook); + + if (type == HOOK_TIMER) + hook->tag = fe_timeout_add (timeout, plugin_timeout_cb, hook); + + return hook; +} + +GList * +plugin_command_list(GList *tmp_list) +{ + xchat_hook *hook; + GSList *list = hook_list; + + while (list) + { + hook = list->data; + if (hook->type == HOOK_COMMAND) + tmp_list = g_list_prepend(tmp_list, hook->name); + list = list->next; + } + return tmp_list; +} + +void +plugin_command_foreach (session *sess, void *userdata, + void (*cb) (session *sess, void *userdata, char *name, char *help)) +{ + GSList *list; + xchat_hook *hook; + + list = hook_list; + while (list) + { + hook = list->data; + if (hook->type == HOOK_COMMAND && hook->name[0]) + { + cb (sess, userdata, hook->name, hook->help_text); + } + list = list->next; + } +} + +int +plugin_show_help (session *sess, char *cmd) +{ + GSList *list; + xchat_hook *hook; + + list = plugin_hook_find (hook_list, HOOK_COMMAND, cmd); + if (list) + { + hook = list->data; + if (hook->help_text) + { + PrintText (sess, hook->help_text); + return 1; + } + } + + return 0; +} + +/* ========================================================= */ +/* ===== these are the functions plugins actually call ===== */ +/* ========================================================= */ + +void * +xchat_unhook (xchat_plugin *ph, xchat_hook *hook) +{ + /* perl.c trips this */ + if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED) + return NULL; + + if (hook->type == HOOK_TIMER && hook->tag != 0) + fe_timeout_remove (hook->tag); + + if (hook->type == HOOK_FD && hook->tag != 0) + fe_input_remove (hook->tag); + + hook->type = HOOK_DELETED; /* expunge later */ + + if (hook->name) + free (hook->name); /* NULL for timers & fds */ + if (hook->help_text) + free (hook->help_text); /* NULL for non-commands */ + + return hook->userdata; +} + +xchat_hook * +xchat_hook_command (xchat_plugin *ph, const char *name, int pri, + xchat_cmd_cb *callb, const char *help_text, void *userdata) +{ + return plugin_add_hook (ph, HOOK_COMMAND, pri, name, help_text, callb, 0, + userdata); +} + +xchat_hook * +xchat_hook_server (xchat_plugin *ph, const char *name, int pri, + xchat_serv_cb *callb, void *userdata) +{ + return plugin_add_hook (ph, HOOK_SERVER, pri, name, 0, callb, 0, userdata); +} + +xchat_hook * +xchat_hook_print (xchat_plugin *ph, const char *name, int pri, + xchat_print_cb *callb, void *userdata) +{ + return plugin_add_hook (ph, HOOK_PRINT, pri, name, 0, callb, 0, userdata); +} + +xchat_hook * +xchat_hook_timer (xchat_plugin *ph, int timeout, xchat_timer_cb *callb, + void *userdata) +{ + return plugin_add_hook (ph, HOOK_TIMER, 0, 0, 0, callb, timeout, userdata); +} + +xchat_hook * +xchat_hook_fd (xchat_plugin *ph, int fd, int flags, + xchat_fd_cb *callb, void *userdata) +{ + xchat_hook *hook; + + hook = plugin_add_hook (ph, HOOK_FD, 0, 0, 0, callb, 0, userdata); + hook->pri = fd; + /* plugin hook_fd flags correspond exactly to FIA_* flags (fe.h) */ + hook->tag = fe_input_add (fd, flags, plugin_fd_cb, hook); + + return hook; +} + +void +xchat_print (xchat_plugin *ph, const char *text) +{ + if (!is_session (ph->context)) + { + DEBUG(PrintTextf(0, "%s\txchat_print called without a valid context.\n", ph->name)); + return; + } + + PrintText (ph->context, (char *)text); +} + +void +xchat_printf (xchat_plugin *ph, const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + xchat_print (ph, buf); + g_free (buf); +} + +void +xchat_command (xchat_plugin *ph, const char *command) +{ + char *conv; + int len = -1; + + if (!is_session (ph->context)) + { + DEBUG(PrintTextf(0, "%s\txchat_command called without a valid context.\n", ph->name)); + return; + } + + /* scripts/plugins continue to send non-UTF8... *sigh* */ + conv = text_validate ((char **)&command, &len); + handle_command (ph->context, (char *)command, FALSE); + g_free (conv); +} + +void +xchat_commandf (xchat_plugin *ph, const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + xchat_command (ph, buf); + g_free (buf); +} + +int +xchat_nickcmp (xchat_plugin *ph, const char *s1, const char *s2) +{ + return ((session *)ph->context)->server->p_cmp (s1, s2); +} + +xchat_context * +xchat_get_context (xchat_plugin *ph) +{ + return ph->context; +} + +int +xchat_set_context (xchat_plugin *ph, xchat_context *context) +{ + if (is_session (context)) + { + ph->context = context; + return 1; + } + return 0; +} + +xchat_context * +xchat_find_context (xchat_plugin *ph, const char *servname, const char *channel) +{ + GSList *slist, *clist, *sessions = NULL; + server *serv; + session *sess; + char *netname; + + if (servname == NULL && channel == NULL) + return current_sess; + + slist = serv_list; + while (slist) + { + serv = slist->data; + netname = server_get_network (serv, TRUE); + + if (servname == NULL || + rfc_casecmp (servname, serv->servername) == 0 || + strcasecmp (servname, serv->hostname) == 0 || + strcasecmp (servname, netname) == 0) + { + if (channel == NULL) + return serv->front_session; + + clist = sess_list; + while (clist) + { + sess = clist->data; + if (sess->server == serv) + { + if (rfc_casecmp (channel, sess->channel) == 0) + { + if (sess->server == ph->context->server) + { + g_slist_free (sessions); + return sess; + } else + { + sessions = g_slist_prepend (sessions, sess); + } + } + } + clist = clist->next; + } + } + slist = slist->next; + } + + if (sessions) + { + sessions = g_slist_reverse (sessions); + sess = sessions->data; + g_slist_free (sessions); + return sess; + } + + return NULL; +} + +const char * +xchat_get_info (xchat_plugin *ph, const char *id) +{ + session *sess; + guint32 hash; + + /* 1234567890 */ + if (!strncmp (id, "event_text", 10)) + { + char *e = (char *)id + 10; + if (*e == ' ') e++; /* 2.8.0 only worked without a space */ + return text_find_format_string (e); + } + + hash = str_hash (id); + /* do the session independant ones first */ + switch (hash) + { + case 0x325acab5: /* libdirfs */ + return XCHATLIBDIR; + + case 0x14f51cd8: /* version */ + return PACKAGE_VERSION; + + case 0xdd9b1abd: /* xchatdir */ + return get_xdir_utf8 (); + + case 0xe33f6c4a: /* xchatdirfs */ + return get_xdir_fs (); + } + + sess = ph->context; + if (!is_session (sess)) + { + DEBUG(PrintTextf(0, "%s\txchat_get_info called without a valid context.\n", ph->name)); + return NULL; + } + + switch (hash) + { + case 0x2de2ee: /* away */ + if (sess->server->is_away) + return sess->server->last_away_reason; + return NULL; + + case 0x2c0b7d03: /* channel */ + return sess->channel; + + case 0x2c0d614c: /* charset */ + { + const char *locale; + + if (sess->server->encoding) + return sess->server->encoding; + + locale = NULL; + g_get_charset (&locale); + return locale; + } + + case 0x30f5a8: /* host */ + return sess->server->hostname; + + case 0x1c0e99c1: /* inputbox */ + return fe_get_inputbox_contents (sess); + + case 0x633fb30: /* modes */ + return sess->current_modes; + + case 0x6de15a2e: /* network */ + return server_get_network (sess->server, FALSE); + + case 0x339763: /* nick */ + return sess->server->nick; + + case 0x438fdf9: /* nickserv */ + if (sess->server->network) + return ((ircnet *)sess->server->network)->nickserv; + return NULL; + + case 0xca022f43: /* server */ + if (!sess->server->connected) + return NULL; + return sess->server->servername; + + case 0x696cd2f: /* topic */ + return sess->topic; + + case 0x3419f12d: /* gtkwin_ptr */ + return fe_gui_info_ptr (sess, 1); + + case 0x506d600b: /* native win_ptr */ + return fe_gui_info_ptr (sess, 0); + + case 0x6d3431b5: /* win_status */ + switch (fe_gui_info (sess, 0)) /* check window status */ + { + case 0: return "normal"; + case 1: return "active"; + case 2: return "hidden"; + } + return NULL; + } + + return NULL; +} + +int +xchat_get_prefs (xchat_plugin *ph, const char *name, const char **string, int *integer) +{ + int i = 0; + + /* some special run-time info (not really prefs, but may aswell throw it in here) */ + switch (str_hash (name)) + { + case 0xf82136c4: /* state_cursor */ + *integer = fe_get_inputbox_cursor (ph->context); + return 2; + + case 0xd1b: /* id */ + *integer = ph->context->server->id; + return 2; + } + + do + { + if (!strcasecmp (name, vars[i].name)) + { + switch (vars[i].type) + { + case TYPE_STR: + *string = ((char *) &prefs + vars[i].offset); + return 1; + + case TYPE_INT: + *integer = *((int *) &prefs + vars[i].offset); + return 2; + + default: + /*case TYPE_BOOL:*/ + if (*((int *) &prefs + vars[i].offset)) + *integer = 1; + else + *integer = 0; + return 3; + } + } + i++; + } + while (vars[i].name); + + return 0; +} + +xchat_list * +xchat_list_get (xchat_plugin *ph, const char *name) +{ + xchat_list *list; + + list = malloc (sizeof (xchat_list)); + list->pos = NULL; + + switch (str_hash (name)) + { + case 0x556423d0: /* channels */ + list->type = LIST_CHANNELS; + list->next = sess_list; + break; + + case 0x183c4: /* dcc */ + list->type = LIST_DCC; + list->next = dcc_list; + break; + + case 0xb90bfdd2: /* ignore */ + list->type = LIST_IGNORE; + list->next = ignore_list; + break; + + case 0xc2079749: /* notify */ + list->type = LIST_NOTIFY; + list->next = notify_list; + list->head = (void *)ph->context; /* reuse this pointer */ + break; + + case 0x6a68e08: /* users */ + if (is_session (ph->context)) + { + list->type = LIST_USERS; + list->head = list->next = userlist_flat_list (ph->context); + fe_userlist_set_selected (ph->context); + break; + } /* fall through */ + + default: + free (list); + return NULL; + } + + return list; +} + +void +xchat_list_free (xchat_plugin *ph, xchat_list *xlist) +{ + if (xlist->type == LIST_USERS) + g_slist_free (xlist->head); + free (xlist); +} + +int +xchat_list_next (xchat_plugin *ph, xchat_list *xlist) +{ + if (xlist->next == NULL) + return 0; + + xlist->pos = xlist->next; + xlist->next = xlist->pos->next; + + /* NOTIFY LIST: Find the entry which matches the context + of the plugin when list_get was originally called. */ + if (xlist->type == LIST_NOTIFY) + { + xlist->notifyps = notify_find_server_entry (xlist->pos->data, + ((session *)xlist->head)->server); + if (!xlist->notifyps) + return 0; + } + + return 1; +} + +const char * const * +xchat_list_fields (xchat_plugin *ph, const char *name) +{ + static const char * const dcc_fields[] = + { + "iaddress32","icps", "sdestfile","sfile", "snick", "iport", + "ipos", "iposhigh", "iresume", "iresumehigh", "isize", "isizehigh", "istatus", "itype", NULL + }; + static const char * const channels_fields[] = + { + "schannel", "schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes", + "snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers", + NULL + }; + static const char * const ignore_fields[] = + { + "iflags", "smask", NULL + }; + static const char * const notify_fields[] = + { + "iflags", "snetworks", "snick", "toff", "ton", "tseen", NULL + }; + static const char * const users_fields[] = + { + "iaway", "shost", "tlasttalk", "snick", "sprefix", "srealname", "iselected", NULL + }; + static const char * const list_of_lists[] = + { + "channels", "dcc", "ignore", "notify", "users", NULL + }; + + switch (str_hash (name)) + { + case 0x556423d0: /* channels */ + return channels_fields; + case 0x183c4: /* dcc */ + return dcc_fields; + case 0xb90bfdd2: /* ignore */ + return ignore_fields; + case 0xc2079749: /* notify */ + return notify_fields; + case 0x6a68e08: /* users */ + return users_fields; + case 0x6236395: /* lists */ + return list_of_lists; + } + + return NULL; +} + +time_t +xchat_list_time (xchat_plugin *ph, xchat_list *xlist, const char *name) +{ + guint32 hash = str_hash (name); + gpointer data; + + switch (xlist->type) + { + case LIST_NOTIFY: + if (!xlist->notifyps) + return (time_t) -1; + switch (hash) + { + case 0x1ad6f: /* off */ + return xlist->notifyps->lastoff; + case 0xddf: /* on */ + return xlist->notifyps->laston; + case 0x35ce7b: /* seen */ + return xlist->notifyps->lastseen; + } + break; + + case LIST_USERS: + data = xlist->pos->data; + switch (hash) + { + case 0xa9118c42: /* lasttalk */ + return ((struct User *)data)->lasttalk; + } + } + + return (time_t) -1; +} + +const char * +xchat_list_str (xchat_plugin *ph, xchat_list *xlist, const char *name) +{ + guint32 hash = str_hash (name); + gpointer data = ph->context; + int type = LIST_CHANNELS; + + /* a NULL xlist is a shortcut to current "channels" context */ + if (xlist) + { + data = xlist->pos->data; + type = xlist->type; + } + + switch (type) + { + case LIST_CHANNELS: + switch (hash) + { + case 0x2c0b7d03: /* channel */ + return ((session *)data)->channel; + case 0x577e0867: /* chantypes */ + return ((session *)data)->server->chantypes; + case 0x38b735af: /* context */ + return data; /* this is a session * */ + case 0x6de15a2e: /* network */ + return server_get_network (((session *)data)->server, FALSE); + case 0x8455e723: /* nickprefixes */ + return ((session *)data)->server->nick_prefixes; + case 0x829689ad: /* nickmodes */ + return ((session *)data)->server->nick_modes; + case 0xca022f43: /* server */ + return ((session *)data)->server->servername; + } + break; + + case LIST_DCC: + switch (hash) + { + case 0x3d9ad31e: /* destfile */ + return ((struct DCC *)data)->destfile; + case 0x2ff57c: /* file */ + return ((struct DCC *)data)->file; + case 0x339763: /* nick */ + return ((struct DCC *)data)->nick; + } + break; + + case LIST_IGNORE: + switch (hash) + { + case 0x3306ec: /* mask */ + return ((struct ignore *)data)->mask; + } + break; + + case LIST_NOTIFY: + switch (hash) + { + case 0x4e49ec05: /* networks */ + return ((struct notify *)data)->networks; + case 0x339763: /* nick */ + return ((struct notify *)data)->name; + } + break; + + case LIST_USERS: + switch (hash) + { + case 0x339763: /* nick */ + return ((struct User *)data)->nick; + case 0x30f5a8: /* host */ + return ((struct User *)data)->hostname; + case 0xc594b292: /* prefix */ + return ((struct User *)data)->prefix; + case 0xccc6d529: /* realname */ + return ((struct User *)data)->realname; + } + break; + } + + return NULL; +} + +int +xchat_list_int (xchat_plugin *ph, xchat_list *xlist, const char *name) +{ + guint32 hash = str_hash (name); + gpointer data = ph->context; + int tmp, type = LIST_CHANNELS; + + /* a NULL xlist is a shortcut to current "channels" context */ + if (xlist) + { + data = xlist->pos->data; + type = xlist->type; + } + + switch (type) + { + case LIST_DCC: + switch (hash) + { + case 0x34207553: /* address32 */ + return ((struct DCC *)data)->addr; + case 0x181a6: /* cps */ + return ((struct DCC *)data)->cps; + case 0x349881: /* port */ + return ((struct DCC *)data)->port; + case 0x1b254: /* pos */ + return ((struct DCC *)data)->pos & 0xffffffff; + case 0xe8a945f6: /* poshigh */ + return (((struct DCC *)data)->pos >> 32) & 0xffffffff; + case 0xc84dc82d: /* resume */ + return ((struct DCC *)data)->resumable & 0xffffffff; + case 0xded4c74f: /* resumehigh */ + return (((struct DCC *)data)->resumable >> 32) & 0xffffffff; + case 0x35e001: /* size */ + return ((struct DCC *)data)->size & 0xffffffff; + case 0x3284d523: /* sizehigh */ + return (((struct DCC *)data)->size >> 32) & 0xffffffff; + case 0xcacdcff2: /* status */ + return ((struct DCC *)data)->dccstat; + case 0x368f3a: /* type */ + return ((struct DCC *)data)->type; + } + break; + + case LIST_IGNORE: + switch (hash) + { + case 0x5cfee87: /* flags */ + return ((struct ignore *)data)->type; + } + break; + + case LIST_CHANNELS: + switch (hash) + { + case 0xd1b: /* id */ + return ((struct session *)data)->server->id; + case 0x5cfee87: /* flags */ + tmp = ((struct session *)data)->alert_taskbar; /* bit 10 */ + tmp <<= 1; + tmp |= ((struct session *)data)->alert_tray; /* 9 */ + tmp <<= 1; + tmp |= ((struct session *)data)->alert_beep; /* 8 */ + tmp <<= 1; + /*tmp |= ((struct session *)data)->color_paste;*/ /* 7 */ + tmp <<= 1; + tmp |= ((struct session *)data)->text_hidejoinpart; /* 6 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->have_idmsg; /* 5 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->have_whox; /* 4 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->end_of_motd;/* 3 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->is_away; /* 2 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->connecting; /* 1 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->connected; /* 0 */ + return tmp; + case 0x1a192: /* lag */ + return ((struct session *)data)->server->lag; + case 0x1916144c: /* maxmodes */ + return ((struct session *)data)->server->modes_per_line; + case 0x66f1911: /* queue */ + return ((struct session *)data)->server->sendq_len; + case 0x368f3a: /* type */ + return ((struct session *)data)->type; + case 0x6a68e08: /* users */ + return ((struct session *)data)->total; + } + break; + + case LIST_NOTIFY: + if (!xlist->notifyps) + return -1; + switch (hash) + { + case 0x5cfee87: /* flags */ + return xlist->notifyps->ison; + } + + case LIST_USERS: + switch (hash) + { + case 0x2de2ee: /* away */ + return ((struct User *)data)->away; + case 0x4705f29b: /* selected */ + return ((struct User *)data)->selected; + } + break; + + } + + return -1; +} + +void * +xchat_plugingui_add (xchat_plugin *ph, const char *filename, + const char *name, const char *desc, + const char *version, char *reserved) +{ +#ifdef USE_PLUGIN + ph = plugin_list_add (NULL, strdup (filename), strdup (name), strdup (desc), + strdup (version), NULL, NULL, TRUE, TRUE); + fe_pluginlist_update (); +#endif + + return ph; +} + +void +xchat_plugingui_remove (xchat_plugin *ph, void *handle) +{ +#ifdef USE_PLUGIN + plugin_free (handle, FALSE, FALSE); +#endif +} + +int +xchat_emit_print (xchat_plugin *ph, const char *event_name, ...) +{ + va_list args; + /* currently only 4 because no events use more than 4. + This can be easily expanded without breaking the API. */ + char *argv[4] = {NULL, NULL, NULL, NULL}; + int i = 0; + + va_start (args, event_name); + while (1) + { + argv[i] = va_arg (args, char *); + if (!argv[i]) + break; + i++; + if (i >= 4) + break; + } + + i = text_emit_by_name ((char *)event_name, ph->context, argv[0], argv[1], + argv[2], argv[3]); + va_end (args); + + return i; +} + +char * +xchat_gettext (xchat_plugin *ph, const char *msgid) +{ + /* so that plugins can use xchat's internal gettext strings. */ + /* e.g. The EXEC plugin uses this on Windows. */ + return _(msgid); +} + +void +xchat_send_modes (xchat_plugin *ph, const char **targets, int ntargets, int modes_per_line, char sign, char mode) +{ + char tbuf[514]; /* modes.c needs 512 + null */ + + send_channel_modes (ph->context, tbuf, (char **)targets, 0, ntargets, sign, mode, modes_per_line); +} + +char * +xchat_strip (xchat_plugin *ph, const char *str, int len, int flags) +{ + return strip_color ((char *)str, len, flags); +} + +void +xchat_free (xchat_plugin *ph, void *ptr) +{ + g_free (ptr); +} diff --git a/src/common/plugin.h b/src/common/plugin.h new file mode 100644 index 00000000..b0c89d1b --- /dev/null +++ b/src/common/plugin.h @@ -0,0 +1,132 @@ +#ifndef XCHAT_COMMONPLUGIN_H +#define XCHAT_COMMONPLUGIN_H + +#ifdef PLUGIN_C +struct _xchat_plugin +{ + /* Keep these insync with xchat-plugin.h */ + /* !!don't change the order, to keep binary compat!! */ + xchat_hook *(*xchat_hook_command) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + const char *help_text, + void *userdata); + xchat_hook *(*xchat_hook_server) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_print) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph, + int timeout, + int (*callback) (void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph, + int fd, + int flags, + int (*callback) (int fd, int flags, void *user_data), + void *userdata); + void *(*xchat_unhook) (xchat_plugin *ph, + xchat_hook *hook); + void (*xchat_print) (xchat_plugin *ph, + const char *text); + void (*xchat_printf) (xchat_plugin *ph, + const char *format, ...); + void (*xchat_command) (xchat_plugin *ph, + const char *command); + void (*xchat_commandf) (xchat_plugin *ph, + const char *format, ...); + int (*xchat_nickcmp) (xchat_plugin *ph, + const char *s1, + const char *s2); + int (*xchat_set_context) (xchat_plugin *ph, + xchat_context *ctx); + xchat_context *(*xchat_find_context) (xchat_plugin *ph, + const char *servname, + const char *channel); + xchat_context *(*xchat_get_context) (xchat_plugin *ph); + const char *(*xchat_get_info) (xchat_plugin *ph, + const char *id); + int (*xchat_get_prefs) (xchat_plugin *ph, + const char *name, + const char **string, + int *integer); + xchat_list * (*xchat_list_get) (xchat_plugin *ph, + const char *name); + void (*xchat_list_free) (xchat_plugin *ph, + xchat_list *xlist); + const char * const * (*xchat_list_fields) (xchat_plugin *ph, + const char *name); + int (*xchat_list_next) (xchat_plugin *ph, + xchat_list *xlist); + const char * (*xchat_list_str) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + int (*xchat_list_int) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + void * (*xchat_plugingui_add) (xchat_plugin *ph, + const char *filename, + const char *name, + const char *desc, + const char *version, + char *reserved); + void (*xchat_plugingui_remove) (xchat_plugin *ph, + void *handle); + int (*xchat_emit_print) (xchat_plugin *ph, + const char *event_name, ...); + void *(*xchat_read_fd) (xchat_plugin *ph); + time_t (*xchat_list_time) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + char *(*xchat_gettext) (xchat_plugin *ph, + const char *msgid); + void (*xchat_send_modes) (xchat_plugin *ph, + const char **targets, + int ntargets, + int modes_per_line, + char sign, + char mode); + char *(*xchat_strip) (xchat_plugin *ph, + const char *str, + int len, + int flags); + void (*xchat_free) (xchat_plugin *ph, + void *ptr); + void *(*xchat_dummy4) (xchat_plugin *ph); + void *(*xchat_dummy3) (xchat_plugin *ph); + void *(*xchat_dummy2) (xchat_plugin *ph); + void *(*xchat_dummy1) (xchat_plugin *ph); + /* PRIVATE FIELDS! */ + void *handle; /* from dlopen */ + char *filename; /* loaded from */ + char *name; + char *desc; + char *version; + session *context; + void *deinit_callback; /* pointer to xchat_plugin_deinit */ + unsigned int fake:1; /* fake plugin. Added by xchat_plugingui_add() */ + unsigned int free_strings:1; /* free name,desc,version? */ +}; +#endif + +char *plugin_load (session *sess, char *filename, char *arg); +void plugin_add (session *sess, char *filename, void *handle, void *init_func, void *deinit_func, char *arg, int fake); +int plugin_kill (char *name, int by_filename); +void plugin_kill_all (void); +void plugin_auto_load (session *sess); +int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]); +int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[]); +int plugin_emit_print (session *sess, char *word[]); +int plugin_emit_dummy_print (session *sess, char *name); +int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, int len, char *string); +GList* plugin_command_list(GList *tmp_list); +int plugin_show_help (session *sess, char *cmd); +void plugin_command_foreach (session *sess, void *userdata, void (*cb) (session *sess, void *userdata, char *name, char *usage)); + +#endif diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c new file mode 100644 index 00000000..f1df6ccd --- /dev/null +++ b/src/common/proto-irc.c @@ -0,0 +1,1260 @@ +/* X-Chat + * Copyright (C) 2002 Peter Zelezny. + * + * 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 + */ + +/* IRC RFC1459(+commonly used extensions) protocol implementation */ + +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> + +#include "xchat.h" +#include "ctcp.h" +#include "fe.h" +#include "ignore.h" +#include "inbound.h" +#include "modes.h" +#include "notify.h" +#include "plugin.h" +#include "server.h" +#include "text.h" +#include "outbound.h" +#include "util.h" +#include "xchatc.h" + + +static void +irc_login (server *serv, char *user, char *realname) +{ + if (serv->password[0]) + tcp_sendf (serv, "PASS %s\r\n", serv->password); + + tcp_sendf (serv, + "NICK %s\r\n" + "USER %s %s %s :%s\r\n", + serv->nick, user, user, serv->servername, realname); +} + +static void +irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3) +{ + /* are all ircd authors idiots? */ + switch (serv->nickservtype) + { + case 0: + tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 1: + tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 2: + tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 3: + tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 4: + /* why couldn't QuakeNet implement one of the existing ones? */ + tcp_sendf (serv, "AUTH %s%s%s\r\n", cmd, arg1, arg2, arg3); + } +} + +static void +irc_ns_identify (server *serv, char *pass) +{ + irc_nickserv (serv, "IDENTIFY", pass, "", ""); +} + +static void +irc_ns_ghost (server *serv, char *usname, char *pass) +{ + if (serv->nickservtype != 4) + irc_nickserv (serv, "GHOST", usname, " ", pass); +} + +static void +irc_join (server *serv, char *channel, char *key) +{ + if (key[0]) + tcp_sendf (serv, "JOIN %s %s\r\n", channel, key); + else + tcp_sendf (serv, "JOIN %s\r\n", channel); +} + +static void +irc_join_list_flush (server *serv, GString *c, GString *k) +{ + char *chanstr, *keystr; + + chanstr = g_string_free (c, FALSE); + keystr = g_string_free (k, FALSE); + if (chanstr[0]) + { + if (keystr[0]) + tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr); + else + tcp_sendf (serv, "JOIN %s\r\n", chanstr); + } + g_free (chanstr); + g_free (keystr); +} + +/* join a whole list of channels & keys, split to multiple lines + * to get around 512 limit */ + +static void +irc_join_list (server *serv, GSList *channels, GSList *keys) +{ + GSList *clist; + GSList *klist; + GString *c = g_string_new (NULL); + GString *k = g_string_new (NULL); + int len; + int add; + int i, j; + + i = j = 0; + len = 9; /* "JOIN<space><space>\r\n" */ + clist = channels; + klist = keys; + + while (clist) + { + /* measure how many bytes this channel would add... */ + if (1) + { + add = strlen (clist->data); + if (i != 0) + add++; /* comma */ + } + + if (klist->data) + { + add += strlen (klist->data); + } + else + { + add++; /* 'x' filler */ + } + + if (j != 0) + add++; /* comma */ + + /* too big? dump buffer and start a fresh one */ + if (len + add > 512) + { + irc_join_list_flush (serv, c, k); + + c = g_string_new (NULL); + k = g_string_new (NULL); + i = j = 0; + len = 9; + } + + /* now actually add it to our GStrings */ + if (1) + { + add = strlen (clist->data); + if (i != 0) + { + add++; + g_string_append_c (c, ','); + } + g_string_append (c, clist->data); + i++; + } + + if (klist->data) + { + add += strlen (klist->data); + if (j != 0) + { + add++; + g_string_append_c (k, ','); + } + g_string_append (k, klist->data); + j++; + } + else + { + add++; + if (j != 0) + { + add++; + g_string_append_c (k, ','); + } + g_string_append_c (k, 'x'); + j++; + } + + len += add; + + klist = klist->next; + clist = clist->next; + } + + irc_join_list_flush (serv, c, k); +} + +static void +irc_part (server *serv, char *channel, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "PART %s :%s\r\n", channel, reason); + else + tcp_sendf (serv, "PART %s\r\n", channel); +} + +static void +irc_quit (server *serv, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "QUIT :%s\r\n", reason); + else + tcp_send_len (serv, "QUIT\r\n", 6); +} + +static void +irc_set_back (server *serv) +{ + tcp_send_len (serv, "AWAY\r\n", 6); +} + +static void +irc_set_away (server *serv, char *reason) +{ + if (reason) + { + if (!reason[0]) + reason = " "; + } + else + { + reason = " "; + } + + tcp_sendf (serv, "AWAY :%s\r\n", reason); +} + +static void +irc_ctcp (server *serv, char *to, char *msg) +{ + tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg); +} + +static void +irc_nctcp (server *serv, char *to, char *msg) +{ + tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg); +} + +static void +irc_cycle (server *serv, char *channel, char *key) +{ + tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key); +} + +static void +irc_kick (server *serv, char *channel, char *nick, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason); + else + tcp_sendf (serv, "KICK %s %s\r\n", channel, nick); +} + +static void +irc_invite (server *serv, char *channel, char *nick) +{ + tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel); +} + +static void +irc_mode (server *serv, char *target, char *mode) +{ + tcp_sendf (serv, "MODE %s %s\r\n", target, mode); +} + +/* find channel info when joined */ + +static void +irc_join_info (server *serv, char *channel) +{ + tcp_sendf (serv, "MODE %s\r\n", channel); +} + +/* initiate userlist retreival */ + +static void +irc_user_list (server *serv, char *channel) +{ + tcp_sendf (serv, "WHO %s\r\n", channel); +} + +/* userhost */ + +static void +irc_userhost (server *serv, char *nick) +{ + tcp_sendf (serv, "USERHOST %s\r\n", nick); +} + +static void +irc_away_status (server *serv, char *channel) +{ + if (serv->have_whox) + tcp_sendf (serv, "WHO %s %%ctnf,152\r\n", channel); + else + tcp_sendf (serv, "WHO %s\r\n", channel); +} + +/*static void +irc_get_ip (server *serv, char *nick) +{ + tcp_sendf (serv, "WHO %s\r\n", nick); +}*/ + + +/* + * Command: WHOIS + * Parameters: [<server>] <nickmask>[,<nickmask>[,...]] + */ +static void +irc_user_whois (server *serv, char *nicks) +{ + tcp_sendf (serv, "WHOIS %s\r\n", nicks); +} + +static void +irc_message (server *serv, char *channel, char *text) +{ + tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text); +} + +static void +irc_action (server *serv, char *channel, char *act) +{ + tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act); +} + +static void +irc_notice (server *serv, char *channel, char *text) +{ + tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text); +} + +static void +irc_topic (server *serv, char *channel, char *topic) +{ + if (!topic) + tcp_sendf (serv, "TOPIC %s :\r\n", channel); + else if (topic[0]) + tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic); + else + tcp_sendf (serv, "TOPIC %s\r\n", channel); +} + +static void +irc_list_channels (server *serv, char *arg, int min_users) +{ + if (arg[0]) + { + tcp_sendf (serv, "LIST %s\r\n", arg); + return; + } + + if (serv->use_listargs) + tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1); + else + tcp_send_len (serv, "LIST\r\n", 6); +} + +static void +irc_names (server *serv, char *channel) +{ + tcp_sendf (serv, "NAMES %s\r\n", channel); +} + +static void +irc_change_nick (server *serv, char *new_nick) +{ + tcp_sendf (serv, "NICK %s\r\n", new_nick); +} + +static void +irc_ping (server *serv, char *to, char *timestring) +{ + if (*to) + tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring); + else + tcp_sendf (serv, "PING %s\r\n", timestring); +} + +static int +irc_raw (server *serv, char *raw) +{ + int len; + char tbuf[4096]; + if (*raw) + { + len = strlen (raw); + if (len < sizeof (tbuf) - 3) + { + len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw); + tcp_send_len (serv, tbuf, len); + } else + { + tcp_send_len (serv, raw, len); + tcp_send_len (serv, "\r\n", 2); + } + return TRUE; + } + return FALSE; +} + +/* ============================================================== */ +/* ======================= IRC INPUT ============================ */ +/* ============================================================== */ + + +static void +channel_date (session *sess, char *chan, char *timestr) +{ + time_t timestamp = (time_t) atol (timestr); + char *tim = ctime (×tamp); + tim[24] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0); +} + +static void +process_numeric (session * sess, int n, + char *word[], char *word_eol[], char *text) +{ + server *serv = sess->server; + /* show whois is the server tab */ + session *whois_sess = serv->server_session; + + /* unless this setting is on */ + if (prefs.irc_whois_front) + whois_sess = serv->front_session; + + switch (n) + { + case 1: + inbound_login_start (sess, word[3], word[1]); + /* if network is PTnet then you must get your IP address + from "001" server message */ + if ((strncmp(word[7], "PTnet", 5) == 0) && + (strncmp(word[8], "IRC", 3) == 0) && + (strncmp(word[9], "Network", 7) == 0) && + (strrchr(word[10], '@') != NULL)) + { + serv->use_who = FALSE; + if (prefs.ip_from_server) + inbound_foundip (sess, strrchr(word[10], '@')+1); + } + + /* use /NICKSERV */ + if (strcasecmp (word[7], "DALnet") == 0 || + strcasecmp (word[7], "BRASnet") == 0) + serv->nickservtype = 1; + + /* use /NS */ + else if (strcasecmp (word[7], "FreeNode") == 0) + serv->nickservtype = 2; + + goto def; + + case 4: /* check the ircd type */ + serv->use_listargs = FALSE; + serv->modes_per_line = 3; /* default to IRC RFC */ + if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */ + { + serv->use_listargs = TRUE; /* use the /list args */ + } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */ + { + serv->use_listargs = TRUE; /* use the /list args */ + serv->modes_per_line = 6; /* allow 6 modes per line */ + } else if (strncmp (word[5], "glx2", 4) == 0) + { + serv->use_listargs = TRUE; /* use the /list args */ + } + goto def; + + case 5: + inbound_005 (serv, word); + goto def; + + case 263: /*Server load is temporarily too heavy */ + if (fe_is_chanwindow (sess->server)) + { + fe_chan_list_end (sess->server); + fe_message (word_eol[5] + 1, FE_MSG_ERROR); + } + goto def; + + case 290: /* CAPAB reply */ + if (strstr (word_eol[1], "IDENTIFY-MSG")) + { + serv->have_idmsg = TRUE; + break; + } + goto def; + + case 301: + inbound_away (serv, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]); + break; + + case 302: + if (serv->skip_next_userhost) + { + char *eq = strchr (word[4], '='); + if (eq) + { + *eq = 0; + if (!serv->p_cmp (word[4] + 1, serv->nick)) + { + char *at = strrchr (eq + 1, '@'); + if (at) + inbound_foundip (sess, at + 1); + } + } + + serv->skip_next_userhost = FALSE; + break; + } + else goto def; + + case 303: + word[4]++; + notify_markonline (serv, word); + break; + + case 305: + inbound_uback (serv); + goto def; + + case 306: + inbound_uaway (serv); + goto def; + + case 312: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0); + else + inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, 0xff); + break; + + case 311: /* WHOIS 1st line */ + serv->inside_whois = 1; + inbound_user_info_start (sess, word[4]); + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5], + word[6], word_eol[8] + 1, 0); + else + inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4], + word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], 0xff); + break; + + case 314: /* WHOWAS */ + inbound_user_info_start (sess, word[4]); + EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5], + word[6], word_eol[8] + 1, 0); + break; + + case 317: + if (!serv->skip_next_whois) + { + time_t timestamp = (time_t) atol (word[6]); + long idle = atol (word[5]); + char *tim; + char outbuf[64]; + + snprintf (outbuf, sizeof (outbuf), + "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60, + idle % 60); + if (timestamp == 0) + EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4], + outbuf, NULL, NULL, 0); + else + { + tim = ctime (×tamp); + tim[19] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4], + outbuf, tim, NULL, 0); + } + } + break; + + case 318: /* END OF WHOIS */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL, + NULL, NULL, 0); + serv->skip_next_whois = 0; + serv->inside_whois = 0; + break; + + case 313: + case 319: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4], + word_eol[5] + 1, NULL, NULL, 0); + break; + + case 307: /* dalnet version */ + case 320: /* :is an identified user */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4], + word_eol[5] + 1, NULL, NULL, 0); + break; + + case 321: + if (!fe_is_chanwindow (sess->server)) + EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0); + break; + + case 322: + if (fe_is_chanwindow (sess->server)) + { + fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1); + } else + { + PrintTextf (serv->server_session, "%-16s %-7d %s\017\n", + word[4], atoi (word[5]), word_eol[6] + 1); + } + break; + + case 323: + if (!fe_is_chanwindow (sess->server)) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0); + else + fe_chan_list_end (sess->server); + break; + + case 324: + sess = find_channel (serv, word[4]); + if (!sess) + sess = serv->server_session; + if (sess->ignore_mode) + sess->ignore_mode = FALSE; + else + EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5], + NULL, NULL, 0); + fe_update_mode_buttons (sess, 't', '-'); + fe_update_mode_buttons (sess, 'n', '-'); + fe_update_mode_buttons (sess, 's', '-'); + fe_update_mode_buttons (sess, 'i', '-'); + fe_update_mode_buttons (sess, 'p', '-'); + fe_update_mode_buttons (sess, 'm', '-'); + fe_update_mode_buttons (sess, 'l', '-'); + fe_update_mode_buttons (sess, 'k', '-'); + handle_mode (serv, word, word_eol, "", TRUE); + break; + + case 329: + sess = find_channel (serv, word[4]); + if (sess) + { + if (sess->ignore_date) + sess->ignore_date = FALSE; + else + channel_date (sess, word[4], word[5]); + } + break; + + case 330: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4], + word_eol[6] + 1, word[5], NULL, 0); + break; + + case 332: + inbound_topic (serv, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]); + break; + + case 333: + inbound_topictime (serv, word[4], word[5], atol (word[6])); + break; + +#if 0 + case 338: /* Undernet Real user@host, Real IP */ + EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6], + (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0); + break; +#endif + + case 341: /* INVITE ACK */ + EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername, + NULL, 0); + break; + + case 352: /* WHO */ + { + unsigned int away = 0; + session *who_sess = find_channel (serv, word[4]); + + if (*word[9] == 'G') + away = 1; + + inbound_user_info (sess, word[4], word[5], word[6], word[7], + word[8], word_eol[11], away); + + /* try to show only user initiated whos */ + if (!who_sess || !who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], + word[2], NULL, 0); + } + break; + + case 354: /* undernet WHOX: used as a reply for irc_away_status */ + { + unsigned int away = 0; + session *who_sess; + + /* irc_away_status sends out a "152" */ + if (!strcmp (word[4], "152")) + { + who_sess = find_channel (serv, word[5]); + + if (*word[7] == 'G') + away = 1; + + /* :SanJose.CA.us.undernet.org 354 z1 152 #zed1 z1 H@ */ + inbound_user_info (sess, word[5], 0, 0, 0, word[6], 0, away); + + /* try to show only user initiated whos */ + if (!who_sess || !who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + } else + goto def; + } + break; + + case 315: /* END OF WHO */ + { + session *who_sess; + who_sess = find_channel (serv, word[4]); + if (who_sess) + { + if (!who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + who_sess->doing_who = FALSE; + } else + { + if (!serv->doing_dns) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + serv->doing_dns = FALSE; + } + } + break; + + case 348: /* +e-list entry */ + if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE)) + goto def; + break; + + case 349: /* end of exemption list */ + sess = find_channel (serv, word[4]); + if (!sess) + { + sess = serv->front_session; + goto def; + } + if (!fe_is_banwindow (sess)) + goto def; + fe_ban_list_end (sess, TRUE); + break; + + case 353: /* NAMES */ + inbound_nameslist (serv, word[5], + (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]); + break; + + case 366: + if (!inbound_nameslist_end (serv, word[4])) + goto def; + break; + + case 367: /* banlist entry */ + inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE); + break; + + case 368: + sess = find_channel (serv, word[4]); + if (!sess) + { + sess = serv->front_session; + goto def; + } + if (!fe_is_banwindow (sess)) + goto def; + fe_ban_list_end (sess, FALSE); + break; + + case 369: /* WHOWAS end */ + case 406: /* WHOWAS error */ + EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0); + serv->inside_whois = 0; + break; + + case 372: /* motd text */ + case 375: /* motd start */ + if (!prefs.skipmotd || serv->motd_skipped) + EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL, + NULL, 0); + break; + + case 376: /* end of motd */ + case 422: /* motd file is missing */ + inbound_login_end (sess, text); + break; + + case 433: /* nickname in use */ + case 432: /* erroneous nickname */ + if (serv->end_of_motd) + goto def; + inbound_next_nick (sess, word[4]); + break; + + case 437: + if (serv->end_of_motd || is_channel (serv, word[4])) + goto def; + inbound_next_nick (sess, word[4]); + break; + + case 471: + EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 473: + EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 474: + EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 475: + EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 601: + notify_set_offline (serv, word[4], FALSE); + break; + + case 605: + notify_set_offline (serv, word[4], TRUE); + break; + + case 600: + case 604: + notify_set_online (serv, word[4]); + break; + + default: + + if (serv->inside_whois && word[4][0]) + { + /* some unknown WHOIS reply, ircd coders make them up weekly */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5], + word[2], NULL, 0); + return; + } + + def: + if (is_channel (serv, word[4])) + { + session *realsess = find_channel (serv, word[4]); + if (!realsess) + realsess = serv->server_session; + EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0); + } else + { + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], + word[2], NULL, 0); + } + } +} + +/* handle named messages that starts with a ':' */ + +static void +process_named_msg (session *sess, char *type, char *word[], char *word_eol[]) +{ + server *serv = sess->server; + char ip[128], nick[NICKLEN]; + char *text, *ex; + int len = strlen (type); + + /* fill in the "ip" and "nick" buffers */ + ex = strchr (word[1], '!'); + if (!ex) /* no '!', must be a server message */ + { + safe_strcpy (ip, word[1], sizeof (ip)); + safe_strcpy (nick, word[1], sizeof (nick)); + } else + { + safe_strcpy (ip, ex + 1, sizeof (ip)); + ex[0] = 0; + safe_strcpy (nick, word[1], sizeof (nick)); + ex[0] = '!'; + } + + if (len == 4) + { + guint32 t; + + t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); + /* this should compile to a bunch of: CMP.L, JE ... nice & fast */ + switch (t) + { + case WORDL('J','O','I','N'): + { + char *chan = word[3]; + + if (*chan == ':') + chan++; + if (!serv->p_cmp (nick, serv->nick)) + inbound_ujoin (serv, chan, nick, ip); + else + inbound_join (serv, chan, nick, ip); + } + return; + + case WORDL('K','I','C','K'): + { + char *kicked = word[4]; + char *reason = word_eol[5]; + if (*kicked) + { + if (*reason == ':') + reason++; + if (!strcmp (kicked, serv->nick)) + inbound_ukick (serv, word[3], nick, reason); + else + inbound_kick (serv, word[3], kicked, nick, reason); + } + } + return; + + case WORDL('K','I','L','L'): + EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0); + return; + + case WORDL('M','O','D','E'): + handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */ + return; + + case WORDL('N','I','C','K'): + inbound_newnick (serv, nick, (word_eol[3][0] == ':') + ? word_eol[3] + 1 : word_eol[3], FALSE); + return; + + case WORDL('P','A','R','T'): + { + char *chan = word[3]; + char *reason = word_eol[4]; + + if (*chan == ':') + chan++; + if (*reason == ':') + reason++; + if (!strcmp (nick, serv->nick)) + inbound_upart (serv, chan, ip, reason); + else + inbound_part (serv, chan, nick, ip, reason); + } + return; + + case WORDL('P','O','N','G'): + inbound_ping_reply (serv->server_session, + (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]); + return; + + case WORDL('Q','U','I','T'): + inbound_quit (serv, nick, ip, + (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]); + return; + } + + goto garbage; + } + + else if (len >= 5) + { + guint32 t; + + t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); + /* this should compile to a bunch of: CMP.L, JE ... nice & fast */ + switch (t) + { + case WORDL('I','N','V','I'): + if (ignore_check (word[1], IG_INVI)) + return; + + if (word[4][0] == ':') + EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick, + serv->servername, NULL, 0); + else + EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick, + serv->servername, NULL, 0); + + return; + + case WORDL('N','O','T','I'): + { + int id = FALSE; /* identified */ + + text = word_eol[4]; + if (*text == ':') + text++; + + if (serv->have_idmsg) + { + if (*text == '+') + { + id = TRUE; + text++; + } else if (*text == '-') + text++; + } + + if (!ignore_check (word[1], IG_NOTI)) + inbound_notice (serv, word[3], nick, text, ip, id); + } + return; + + case WORDL('P','R','I','V'): + { + char *to = word[3]; + int len; + int id = FALSE; /* identified */ + if (*to) + { + text = word_eol[4]; + if (*text == ':') + text++; + if (serv->have_idmsg) + { + if (*text == '+') + { + id = TRUE; + text++; + } else if (*text == '-') + text++; + } + len = strlen (text); + if (text[0] == 1 && text[len - 1] == 1) /* ctcp */ + { + text[len - 1] = 0; + text++; + if (strncasecmp (text, "ACTION", 6) != 0) + flood_check (nick, ip, serv, sess, 0); + if (strncasecmp (text, "DCC ", 4) == 0) + /* redo this with handle_quotes TRUE */ + process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE); + ctcp_handle (sess, to, nick, ip, text, word, word_eol, id); + } else + { + if (is_channel (serv, to)) + { + if (ignore_check (word[1], IG_CHAN)) + return; + inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id); + } else + { + if (ignore_check (word[1], IG_PRIV)) + return; + inbound_privmsg (serv, nick, ip, text, id); + } + } + } + } + return; + + case WORDL('T','O','P','I'): + inbound_topicnew (serv, nick, word[3], + (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]); + return; + + case WORDL('W','A','L','L'): + text = word_eol[3]; + if (*text == ':') + text++; + EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0); + return; + } + } + +garbage: + /* unknown message */ + PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]); +} + +/* handle named messages that DON'T start with a ':' */ + +static void +process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[]) +{ + sess = sess->server->server_session; + + if (!strncmp (buf, "PING ", 5)) + { + tcp_sendf (sess->server, "PONG %s\r\n", buf + 5); + return; + } + if (!strncmp (buf, "ERROR", 5)) + { + EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0); + return; + } + if (!strncmp (buf, "NOTICE ", 7)) + { + buf = word_eol[3]; + if (*buf == ':') + buf++; + EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0); + return; + } + + EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0); +} + +/* irc_inline() - 1 single line received from serv */ + +static void +irc_inline (server *serv, char *buf, int len) +{ + session *sess, *tmp; + char *type, *text; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */ + char *pdibuf = pdibuf_static; + + /* need more than 522? fall back to malloc */ + if (len >= sizeof (pdibuf_static)) + pdibuf = malloc (len + 1); + + sess = serv->front_session; + + /* Python relies on this */ + word[PDIWORDS] = NULL; + word_eol[PDIWORDS] = NULL; + + if (buf[0] == ':') + { + /* split line into words and words_to_end_of_line */ + process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE); + + /* find a context for this message */ + if (is_channel (serv, word[3])) + { + tmp = find_channel (serv, word[3]); + if (tmp) + sess = tmp; + } + + /* for server messages, the 2nd word is the "message type" */ + type = word[2]; + + word[0] = type; + word_eol[1] = buf; /* keep the ":" for plugins */ + if (plugin_emit_server (sess, type, word, word_eol)) + goto xit; + word[1]++; + word_eol[1] = buf + 1; /* but not for xchat internally */ + + } else + { + process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE); + word[0] = type = word[1]; + if (plugin_emit_server (sess, type, word, word_eol)) + goto xit; + } + + if (buf[0] != ':') + { + process_named_servermsg (sess, buf, word[0], word_eol); + goto xit; + } + + /* see if the second word is a numeric */ + if (isdigit ((unsigned char) word[2][0])) + { + text = word_eol[4]; + if (*text == ':') + text++; + + process_numeric (sess, atoi (word[2]), word, word_eol, text); + } else + { + process_named_msg (sess, type, word, word_eol); + } + +xit: + if (pdibuf != pdibuf_static) + free (pdibuf); +} + +void +proto_fill_her_up (server *serv) +{ + serv->p_inline = irc_inline; + serv->p_invite = irc_invite; + serv->p_cycle = irc_cycle; + serv->p_ctcp = irc_ctcp; + serv->p_nctcp = irc_nctcp; + serv->p_quit = irc_quit; + serv->p_kick = irc_kick; + serv->p_part = irc_part; + serv->p_ns_identify = irc_ns_identify; + serv->p_ns_ghost = irc_ns_ghost; + serv->p_join = irc_join; + serv->p_join_list = irc_join_list; + serv->p_login = irc_login; + serv->p_join_info = irc_join_info; + serv->p_mode = irc_mode; + serv->p_user_list = irc_user_list; + serv->p_away_status = irc_away_status; + /*serv->p_get_ip = irc_get_ip;*/ + serv->p_whois = irc_user_whois; + serv->p_get_ip = irc_user_list; + serv->p_get_ip_uh = irc_userhost; + serv->p_set_back = irc_set_back; + serv->p_set_away = irc_set_away; + serv->p_message = irc_message; + serv->p_action = irc_action; + serv->p_notice = irc_notice; + serv->p_topic = irc_topic; + serv->p_list_channels = irc_list_channels; + serv->p_change_nick = irc_change_nick; + serv->p_names = irc_names; + serv->p_ping = irc_ping; + serv->p_raw = irc_raw; + serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */ +} diff --git a/src/common/proto-irc.h b/src/common/proto-irc.h new file mode 100644 index 00000000..90803594 --- /dev/null +++ b/src/common/proto-irc.h @@ -0,0 +1,6 @@ +#ifndef XCHAT_PROTO_H +#define XCHAT_PROTO_H + +void proto_fill_her_up (server *serv); + +#endif diff --git a/src/common/server.c b/src/common/server.c new file mode 100644 index 00000000..dd43adff --- /dev/null +++ b/src/common/server.c @@ -0,0 +1,2047 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 + * + * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru> + * based on Dante source code + * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + * Inferno Nettverk A/S, Norway. All rights reserved. + */ + +/*#define DEBUG_MSPROXY*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#include "inet.h" + +#ifndef WIN32 +#include <signal.h> +#include <sys/wait.h> +#else +#include <winbase.h> +#endif + +#include "xchat.h" +#include "fe.h" +#include "cfgfiles.h" +#include "network.h" +#include "notify.h" +#include "xchatc.h" +#include "inbound.h" +#include "outbound.h" +#include "text.h" +#include "util.h" +#include "url.h" +#include "proto-irc.h" +#include "servlist.h" +#include "server.h" + +#ifdef USE_OPENSSL +#include <openssl/ssl.h> /* SSL_() */ +#include <openssl/err.h> /* ERR_() */ +#include "ssl.h" +#endif + +#ifdef USE_MSPROXY +#include "msproxy.h" +#endif + +#ifdef WIN32 +#include "identd.c" +#endif + +#ifdef USE_LIBPROXY +#include <proxy.h> +#endif + +#ifdef USE_OPENSSL +extern SSL_CTX *ctx; /* xchat.c */ +/* local variables */ +static struct session *g_sess = NULL; +#endif + +static GSList *away_list = NULL; +GSList *serv_list = NULL; + +static void auto_reconnect (server *serv, int send_quit, int err); +static void server_disconnect (session * sess, int sendquit, int err); +static int server_cleanup (server * serv); +static void server_connect (server *serv, char *hostname, int port, int no_login); + +#ifdef USE_LIBPROXY +extern pxProxyFactory *libproxy_factory; +#endif + +/* actually send to the socket. This might do a character translation or + send via SSL. server/dcc both use this function. */ + +int +tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len) +{ + int ret; + char *locale; + gsize loc_len; + + if (encoding == NULL) /* system */ + { + locale = NULL; + if (!prefs.utf8_locale) + { + const gchar *charset; + + g_get_charset (&charset); + locale = g_convert_with_fallback (buf, len, charset, "UTF-8", + "?", 0, &loc_len, 0); + } + } else + { + if (using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */ + /* if all chars fit inside CP1252, use that. Otherwise this + returns NULL and we send UTF-8. */ + locale = g_convert (buf, len, "CP1252", "UTF-8", 0, &loc_len, 0); + else + locale = g_convert_with_fallback (buf, len, encoding, "UTF-8", + "?", 0, &loc_len, 0); + } + + if (locale) + { + len = loc_len; +#ifdef USE_OPENSSL + if (!ssl) + ret = send (sok, locale, len, 0); + else + ret = _SSL_send (ssl, locale, len); +#else + ret = send (sok, locale, len, 0); +#endif + g_free (locale); + } else + { +#ifdef USE_OPENSSL + if (!ssl) + ret = send (sok, buf, len, 0); + else + ret = _SSL_send (ssl, buf, len); +#else + ret = send (sok, buf, len, 0); +#endif + } + + return ret; +} + +static int +server_send_real (server *serv, char *buf, int len) +{ + fe_add_rawlog (serv, buf, len, TRUE); + + return tcp_send_real (serv->ssl, serv->sok, serv->encoding, serv->using_irc, + buf, len); +} + +/* new throttling system, uses the same method as the Undernet + ircu2.10 server; under test, a 200-line paste didn't flood + off the client */ + +static int +tcp_send_queue (server *serv) +{ + char *buf, *p; + int len, i, pri; + GSList *list; + time_t now = time (0); + + /* did the server close since the timeout was added? */ + if (!is_server (serv)) + return 0; + + /* try priority 2,1,0 */ + pri = 2; + while (pri >= 0) + { + list = serv->outbound_queue; + while (list) + { + buf = (char *) list->data; + if (buf[0] == pri) + { + buf++; /* skip the priority byte */ + len = strlen (buf); + + if (serv->next_send < now) + serv->next_send = now; + if (serv->next_send - now >= 10) + { + /* check for clock skew */ + if (now >= serv->prev_now) + return 1; /* don't remove the timeout handler */ + /* it is skewed, reset to something sane */ + serv->next_send = now; + } + + for (p = buf, i = len; i && *p != ' '; p++, i--); + serv->next_send += (2 + i / 120); + serv->sendq_len -= len; + serv->prev_now = now; + fe_set_throttle (serv); + + server_send_real (serv, buf, len); + + buf--; + serv->outbound_queue = g_slist_remove (serv->outbound_queue, buf); + free (buf); + list = serv->outbound_queue; + } else + { + list = list->next; + } + } + /* now try pri 0 */ + pri--; + } + return 0; /* remove the timeout handler */ +} + +int +tcp_send_len (server *serv, char *buf, int len) +{ + char *dbuf; + int noqueue = !serv->outbound_queue; + + if (!prefs.throttle) + return server_send_real (serv, buf, len); + + dbuf = malloc (len + 2); /* first byte is the priority */ + dbuf[0] = 2; /* pri 2 for most things */ + memcpy (dbuf + 1, buf, len); + dbuf[len + 1] = 0; + + /* privmsg and notice get a lower priority */ + if (strncasecmp (dbuf + 1, "PRIVMSG", 7) == 0 || + strncasecmp (dbuf + 1, "NOTICE", 6) == 0) + { + dbuf[0] = 1; + } + else + { + /* WHO/MODE get the lowest priority */ + if (strncasecmp (dbuf + 1, "WHO ", 4) == 0 || + /* but only MODE queries, not changes */ + (strncasecmp (dbuf + 1, "MODE", 4) == 0 && + strchr (dbuf, '-') == NULL && + strchr (dbuf, '+') == NULL)) + dbuf[0] = 0; + } + + serv->outbound_queue = g_slist_append (serv->outbound_queue, dbuf); + serv->sendq_len += len; /* tcp_send_queue uses strlen */ + + if (tcp_send_queue (serv) && noqueue) + fe_timeout_add (500, tcp_send_queue, serv); + + return 1; +} + +/*int +tcp_send (server *serv, char *buf) +{ + return tcp_send_len (serv, buf, strlen (buf)); +}*/ + +void +tcp_sendf (server *serv, char *fmt, ...) +{ + va_list args; + /* keep this buffer in BSS. Converting UTF-8 to ISO-8859-x might make the + string shorter, so allow alot more than 512 for now. */ + static char send_buf[1540]; /* good code hey (no it's not overflowable) */ + int len; + + va_start (args, fmt); + len = vsnprintf (send_buf, sizeof (send_buf) - 1, fmt, args); + va_end (args); + + send_buf[sizeof (send_buf) - 1] = '\0'; + if (len < 0 || len > (sizeof (send_buf) - 1)) + len = strlen (send_buf); + + tcp_send_len (serv, send_buf, len); +} + +static int +close_socket_cb (gpointer sok) +{ + closesocket (GPOINTER_TO_INT (sok)); + return 0; +} + +static void +close_socket (int sok) +{ + /* close the socket in 5 seconds so the QUIT message is not lost */ + fe_timeout_add (5000, close_socket_cb, GINT_TO_POINTER (sok)); +} + +/* handle 1 line of text received from the server */ + +static void +server_inline (server *serv, char *line, int len) +{ + char *utf_line_allocated = NULL; + + /* Checks whether we're set to use UTF-8 charset */ + if (serv->using_irc || /* 1. using CP1252/UTF-8 Hybrid */ + (serv->encoding == NULL && prefs.utf8_locale) || /* OR 2. using system default->UTF-8 */ + (serv->encoding != NULL && /* OR 3. explicitly set to UTF-8 */ + (strcasecmp (serv->encoding, "UTF8") == 0 || + strcasecmp (serv->encoding, "UTF-8") == 0))) + { + /* The user has the UTF-8 charset set, either via /charset + command or from his UTF-8 locale. Thus, we first try the + UTF-8 charset, and if we fail to convert, we assume + it to be ISO-8859-1 (see text_validate). */ + + utf_line_allocated = text_validate (&line, &len); + + } else + { + /* Since the user has an explicit charset set, either + via /charset command or from his non-UTF8 locale, + we don't fallback to ISO-8859-1 and instead try to remove + errnoeous octets till the string is convertable in the + said charset. */ + + const char *encoding = NULL; + + if (serv->encoding != NULL) + encoding = serv->encoding; + else + g_get_charset (&encoding); + + if (encoding != NULL) + { + char *conv_line; /* holds a copy of the original string */ + int conv_len; /* tells g_convert how much of line to convert */ + gsize utf_len; + gsize read_len; + GError *err; + gboolean retry; + + conv_line = g_malloc (len + 1); + memcpy (conv_line, line, len); + conv_line[len] = 0; + conv_len = len; + + /* if CP1255, convert it with the NUL terminator. + Works around SF bug #1122089 */ + if (serv->using_cp1255) + conv_len++; + + do + { + err = NULL; + retry = FALSE; + utf_line_allocated = g_convert_with_fallback (conv_line, conv_len, "UTF-8", encoding, "?", &read_len, &utf_len, &err); + if (err != NULL) + { + if (err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE && conv_len > (read_len + 1)) + { + /* Make our best bet by removing the erroneous char. + This will work for casual 8-bit strings with non-standard chars. */ + memmove (conv_line + read_len, conv_line + read_len + 1, conv_len - read_len -1); + conv_len--; + retry = TRUE; + } + g_error_free (err); + } + } while (retry); + + g_free (conv_line); + + /* If any conversion has occured at all. Conversion might fail + due to errors other than invalid sequences, e.g. unknown charset. */ + if (utf_line_allocated != NULL) + { + line = utf_line_allocated; + len = utf_len; + if (serv->using_cp1255 && len > 0) + len--; + } + else + { + /* If all fails, treat as UTF-8 with fallback to ISO-8859-1. */ + utf_line_allocated = text_validate (&line, &len); + } + } + } + + fe_add_rawlog (serv, line, len, FALSE); + url_check_line (line, len); + + /* let proto-irc.c handle it */ + serv->p_inline (serv, line, len); + + if (utf_line_allocated != NULL) /* only if a special copy was allocated */ + g_free (utf_line_allocated); +} + +/* read data from socket */ + +static gboolean +server_read (GIOChannel *source, GIOCondition condition, server *serv) +{ + int sok = serv->sok; + int error, i, len; + char lbuf[2050]; + + while (1) + { +#ifdef USE_OPENSSL + if (!serv->ssl) +#endif + len = recv (sok, lbuf, sizeof (lbuf) - 2, 0); +#ifdef USE_OPENSSL + else + len = _SSL_recv (serv->ssl, lbuf, sizeof (lbuf) - 2); +#endif + if (len < 1) + { + error = 0; + if (len < 0) + { + if (would_block ()) + return TRUE; + error = sock_error (); + } + if (!serv->end_of_motd) + { + server_disconnect (serv->server_session, FALSE, error); + if (!servlist_cycle (serv)) + { + if (prefs.autoreconnect) + auto_reconnect (serv, FALSE, error); + } + } else + { + if (prefs.autoreconnect) + auto_reconnect (serv, FALSE, error); + else + server_disconnect (serv->server_session, FALSE, error); + } + return TRUE; + } + + i = 0; + + lbuf[len] = 0; + + while (i < len) + { + switch (lbuf[i]) + { + case '\r': + break; + + case '\n': + serv->linebuf[serv->pos] = 0; + server_inline (serv, serv->linebuf, serv->pos); + serv->pos = 0; + break; + + default: + serv->linebuf[serv->pos] = lbuf[i]; + if (serv->pos >= (sizeof (serv->linebuf) - 1)) + fprintf (stderr, + "*** XCHAT WARNING: Buffer overflow - shit server!\n"); + else + serv->pos++; + } + i++; + } + } +} + +static void +server_connected (server * serv) +{ + prefs.wait_on_exit = TRUE; + serv->ping_recv = time (0); + serv->connected = TRUE; + set_nonblocking (serv->sok); + serv->iotag = fe_input_add (serv->sok, FIA_READ|FIA_EX, server_read, serv); + if (!serv->no_login) + { + EMIT_SIGNAL (XP_TE_CONNECTED, serv->server_session, NULL, NULL, NULL, + NULL, 0); + if (serv->network) + { + serv->p_login (serv, + (!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) && + (((ircnet *)serv->network)->user)) ? + (((ircnet *)serv->network)->user) : + prefs.username, + (!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) && + (((ircnet *)serv->network)->real)) ? + (((ircnet *)serv->network)->real) : + prefs.realname); + } else + { + serv->p_login (serv, prefs.username, prefs.realname); + } + } else + { + EMIT_SIGNAL (XP_TE_SERVERCONNECTED, serv->server_session, NULL, NULL, + NULL, NULL, 0); + } + + server_set_name (serv, serv->servername); + fe_server_event (serv, FE_SE_CONNECT, 0); +} + +#ifdef WIN32 + +static gboolean +server_close_pipe (int *pipefd) /* see comments below */ +{ + close (pipefd[0]); /* close WRITE end first to cause an EOF on READ */ + close (pipefd[1]); /* in giowin32, and end that thread. */ + free (pipefd); + return FALSE; +} + +#endif + +static void +server_stopconnecting (server * serv) +{ + if (serv->iotag) + { + fe_input_remove (serv->iotag); + serv->iotag = 0; + } + + if (serv->joindelay_tag) + { + fe_timeout_remove (serv->joindelay_tag); + serv->joindelay_tag = 0; + } + +#ifndef WIN32 + /* kill the child process trying to connect */ + kill (serv->childpid, SIGKILL); + waitpid (serv->childpid, NULL, 0); + + close (serv->childwrite); + close (serv->childread); +#else + PostThreadMessage (serv->childpid, WM_QUIT, 0, 0); + + { + /* if we close the pipe now, giowin32 will crash. */ + int *pipefd = malloc (sizeof (int) * 2); + pipefd[0] = serv->childwrite; + pipefd[1] = serv->childread; + g_idle_add ((GSourceFunc)server_close_pipe, pipefd); + } +#endif + +#ifdef USE_OPENSSL + if (serv->ssl_do_connect_tag) + { + fe_timeout_remove (serv->ssl_do_connect_tag); + serv->ssl_do_connect_tag = 0; + } +#endif + + fe_progressbar_end (serv); + + serv->connecting = FALSE; + fe_server_event (serv, FE_SE_DISCONNECT, 0); +} + +#ifdef USE_OPENSSL +#define SSLTMOUT 90 /* seconds */ +static void +ssl_cb_info (SSL * s, int where, int ret) +{ +/* char buf[128];*/ + + + return; /* FIXME: make debug level adjustable in serverlist or settings */ + +/* snprintf (buf, sizeof (buf), "%s (%d)", SSL_state_string_long (s), where); + if (g_sess) + EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); + else + fprintf (stderr, "%s\n", buf);*/ +} + +static int +ssl_cb_verify (int ok, X509_STORE_CTX * ctx) +{ + char subject[256]; + char issuer[256]; + char buf[512]; + + + X509_NAME_oneline (X509_get_subject_name (ctx->current_cert), subject, + sizeof (subject)); + X509_NAME_oneline (X509_get_issuer_name (ctx->current_cert), issuer, + sizeof (issuer)); + + snprintf (buf, sizeof (buf), "* Subject: %s", subject); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); + snprintf (buf, sizeof (buf), "* Issuer: %s", issuer); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); + + return (TRUE); /* always ok */ +} + +static int +ssl_do_connect (server * serv) +{ + char buf[128]; + + g_sess = serv->server_session; + if (SSL_connect (serv->ssl) <= 0) + { + char err_buf[128]; + int err; + + g_sess = NULL; + if ((err = ERR_get_error ()) > 0) + { + ERR_error_string (err, err_buf); + snprintf (buf, sizeof (buf), "(%d) %s", err, err_buf); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, + NULL, NULL, 0); + + if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER) + PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n")); + + server_cleanup (serv); + + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + + return (0); /* remove it (0) */ + } + } + g_sess = NULL; + + if (SSL_is_init_finished (serv->ssl)) + { + struct cert_info cert_info; + struct chiper_info *chiper_info; + int verify_error; + int i; + + if (!_SSL_get_cert_info (&cert_info, serv->ssl)) + { + snprintf (buf, sizeof (buf), "* Certification info:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + snprintf (buf, sizeof (buf), " Subject:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + for (i = 0; cert_info.subject_word[i]; i++) + { + snprintf (buf, sizeof (buf), " %s", cert_info.subject_word[i]); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } + snprintf (buf, sizeof (buf), " Issuer:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + for (i = 0; cert_info.issuer_word[i]; i++) + { + snprintf (buf, sizeof (buf), " %s", cert_info.issuer_word[i]); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } + snprintf (buf, sizeof (buf), " Public key algorithm: %s (%d bits)", + cert_info.algorithm, cert_info.algorithm_bits); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + /*if (cert_info.rsa_tmp_bits) + { + snprintf (buf, sizeof (buf), + " Public key algorithm uses ephemeral key with %d bits", + cert_info.rsa_tmp_bits); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + }*/ + snprintf (buf, sizeof (buf), " Sign algorithm %s", + cert_info.sign_algorithm/*, cert_info.sign_algorithm_bits*/); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + snprintf (buf, sizeof (buf), " Valid since %s to %s", + cert_info.notbefore, cert_info.notafter); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } else + { + snprintf (buf, sizeof (buf), " * No Certificate"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } + + chiper_info = _SSL_get_cipher_info (serv->ssl); /* static buffer */ + snprintf (buf, sizeof (buf), "* Cipher info:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, + 0); + snprintf (buf, sizeof (buf), " Version: %s, cipher %s (%u bits)", + chiper_info->version, chiper_info->chiper, + chiper_info->chiper_bits); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, + 0); + + verify_error = SSL_get_verify_result (serv->ssl); + switch (verify_error) + { + case X509_V_OK: + /* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */ + /* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */ + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_CERT_HAS_EXPIRED: + if (serv->accept_invalid_cert) + { + snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored", + X509_verify_cert_error_string (verify_error), + verify_error); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + break; + } + default: + snprintf (buf, sizeof (buf), "%s.? (%d)", + X509_verify_cert_error_string (verify_error), + verify_error); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL, + NULL, 0); + + server_cleanup (serv); + + return (0); + } + + server_stopconnecting (serv); + + /* activate gtk poll */ + server_connected (serv); + + return (0); /* remove it (0) */ + } else + { + if (serv->ssl->session && serv->ssl->session->time + SSLTMOUT < time (NULL)) + { + snprintf (buf, sizeof (buf), "SSL handshake timed out"); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, + NULL, NULL, 0); + server_cleanup (serv); /* ->connecting = FALSE */ + + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + + return (0); /* remove it (0) */ + } + + return (1); /* call it more (1) */ + } +} +#endif + +static int +timeout_auto_reconnect (server *serv) +{ + if (is_server (serv)) /* make sure it hasnt been closed during the delay */ + { + serv->recondelay_tag = 0; + if (!serv->connected && !serv->connecting && serv->server_session) + { + server_connect (serv, serv->hostname, serv->port, FALSE); + } + } + return 0; /* returning 0 should remove the timeout handler */ +} + +static void +auto_reconnect (server *serv, int send_quit, int err) +{ + session *s; + GSList *list; + int del; + + if (serv->server_session == NULL) + return; + + list = sess_list; + while (list) /* make sure auto rejoin can work */ + { + s = list->data; + if (s->type == SESS_CHANNEL && s->channel[0]) + { + strcpy (s->waitchannel, s->channel); + strcpy (s->willjoinchannel, s->channel); + } + list = list->next; + } + + if (serv->connected) + server_disconnect (serv->server_session, send_quit, err); + + del = prefs.recon_delay * 1000; + if (del < 1000) + del = 500; /* so it doesn't block the gui */ + +#ifndef WIN32 + if (err == -1 || err == 0 || err == ECONNRESET || err == ETIMEDOUT) +#else + if (err == -1 || err == 0 || err == WSAECONNRESET || err == WSAETIMEDOUT) +#endif + serv->reconnect_away = serv->is_away; + + /* is this server in a reconnect delay? remove it! */ + if (serv->recondelay_tag) + { + fe_timeout_remove (serv->recondelay_tag); + serv->recondelay_tag = 0; + } + + serv->recondelay_tag = fe_timeout_add (del, timeout_auto_reconnect, serv); + fe_server_event (serv, FE_SE_RECONDELAY, del); +} + +static void +server_flush_queue (server *serv) +{ + list_free (&serv->outbound_queue); + serv->sendq_len = 0; + fe_set_throttle (serv); +} + +#ifdef WIN32 + +static int +waitline2 (GIOChannel *source, char *buf, int bufsize) +{ + int i = 0; + int len; + + while (1) + { + if (g_io_channel_read (source, &buf[i], 1, &len) != G_IO_ERROR_NONE) + return -1; + if (buf[i] == '\n' || bufsize == i + 1) + { + buf[i] = 0; + return i; + } + i++; + } +} + +#else + +#define waitline2(source,buf,size) waitline(serv->childread,buf,size,0) + +#endif + +/* connect() successed */ + +static void +server_connect_success (server *serv) +{ +#ifdef USE_OPENSSL +#define SSLDOCONNTMOUT 300 + if (serv->use_ssl) + { + char *err; + + /* it'll be a memory leak, if connection isn't terminated by + server_cleanup() */ + serv->ssl = _SSL_socket (ctx, serv->sok); + if ((err = _SSL_set_verify (ctx, ssl_cb_verify, NULL))) + { + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, err, NULL, + NULL, NULL, 0); + server_cleanup (serv); /* ->connecting = FALSE */ + return; + } + /* FIXME: it'll be needed by new servers */ + /* send(serv->sok, "STLS\r\n", 6, 0); sleep(1); */ + set_nonblocking (serv->sok); + serv->ssl_do_connect_tag = fe_timeout_add (SSLDOCONNTMOUT, + ssl_do_connect, serv); + return; + } + + serv->ssl = NULL; +#endif + server_stopconnecting (serv); /* ->connecting = FALSE */ + /* activate glib poll */ + server_connected (serv); +} + +/* receive info from the child-process about connection progress */ + +static gboolean +server_read_child (GIOChannel *source, GIOCondition condition, server *serv) +{ + session *sess = serv->server_session; + char tbuf[128]; + char outbuf[512]; + char host[100]; + char ip[100]; + char *p; + + waitline2 (source, tbuf, sizeof tbuf); + + switch (tbuf[0]) + { + case '0': /* print some text */ + waitline2 (source, tbuf, sizeof tbuf); + PrintText (serv->server_session, tbuf); + break; + case '1': /* unknown host */ + server_stopconnecting (serv); + closesocket (serv->sok4); + if (serv->proxy_sok4 != -1) + closesocket (serv->proxy_sok4); +#ifdef USE_IPV6 + if (serv->sok6 != -1) + closesocket (serv->sok6); + if (serv->proxy_sok6 != -1) + closesocket (serv->proxy_sok6); +#endif + EMIT_SIGNAL (XP_TE_UKNHOST, sess, NULL, NULL, NULL, NULL, 0); + if (!servlist_cycle (serv)) + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + break; + case '2': /* connection failed */ + waitline2 (source, tbuf, sizeof tbuf); + server_stopconnecting (serv); + closesocket (serv->sok4); + if (serv->proxy_sok4 != -1) + closesocket (serv->proxy_sok4); +#ifdef USE_IPV6 + if (serv->sok6 != -1) + closesocket (serv->sok6); + if (serv->proxy_sok6 != -1) + closesocket (serv->proxy_sok6); +#endif + EMIT_SIGNAL (XP_TE_CONNFAIL, sess, errorstring (atoi (tbuf)), NULL, + NULL, NULL, 0); + if (!servlist_cycle (serv)) + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + break; + case '3': /* gethostbyname finished */ + waitline2 (source, host, sizeof host); + waitline2 (source, ip, sizeof ip); + waitline2 (source, outbuf, sizeof outbuf); + EMIT_SIGNAL (XP_TE_CONNECT, sess, host, ip, outbuf, NULL, 0); +#ifdef WIN32 + if (prefs.identd) + { + if (serv->network) + identd_start ((((ircnet *)serv->network)->user) ? + (((ircnet *)serv->network)->user) : + prefs.username); + else + identd_start (prefs.username); + } +#else + snprintf (outbuf, sizeof (outbuf), "%s/auth/xchat_auth", + g_get_home_dir ()); + if (access (outbuf, X_OK) == 0) + { + snprintf (outbuf, sizeof (outbuf), "exec -d %s/auth/xchat_auth %s", + g_get_home_dir (), prefs.username); + handle_command (serv->server_session, outbuf, FALSE); + } +#endif + break; + case '4': /* success */ + waitline2 (source, tbuf, sizeof (tbuf)); +#ifdef USE_MSPROXY + serv->sok = strtol (tbuf, &p, 10); + if (*p++ == ' ') + { + serv->proxy_sok = strtol (p, &p, 10); + serv->msp_state.clientid = strtol (++p, &p, 10); + serv->msp_state.serverid = strtol (++p, &p, 10); + serv->msp_state.seq_sent = atoi (++p); + } else + serv->proxy_sok = -1; +#ifdef DEBUG_MSPROXY + printf ("Parent got main socket: %d, proxy socket: %d\n", serv->sok, serv->proxy_sok); + printf ("Client ID 0x%08x server ID 0x%08x seq_sent %d\n", serv->msp_state.clientid, serv->msp_state.serverid, serv->msp_state.seq_sent); +#endif +#else + serv->sok = atoi (tbuf); +#endif +#ifdef USE_IPV6 + /* close the one we didn't end up using */ + if (serv->sok == serv->sok4) + closesocket (serv->sok6); + else + closesocket (serv->sok4); + if (serv->proxy_sok != -1) + { + if (serv->proxy_sok == serv->proxy_sok4) + closesocket (serv->proxy_sok6); + else + closesocket (serv->proxy_sok4); + } +#endif + server_connect_success (serv); + break; + case '5': /* prefs ip discovered */ + waitline2 (source, tbuf, sizeof tbuf); + prefs.local_ip = inet_addr (tbuf); + break; + case '7': /* gethostbyname (prefs.hostname) failed */ + sprintf (outbuf, + _("Cannot resolve hostname %s\nCheck your IP Settings!\n"), + prefs.hostname); + PrintText (sess, outbuf); + break; + case '8': + PrintText (sess, _("Proxy traversal failed.\n")); + server_disconnect (sess, FALSE, -1); + break; + case '9': + waitline2 (source, tbuf, sizeof tbuf); + EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, tbuf, NULL, NULL, NULL, 0); + break; + } + + return TRUE; +} + +/* kill all sockets & iotags of a server. Stop a connection attempt, or + disconnect if already connected. */ + +static int +server_cleanup (server * serv) +{ + fe_set_lag (serv, 0.0); + + if (serv->iotag) + { + fe_input_remove (serv->iotag); + serv->iotag = 0; + } + + if (serv->joindelay_tag) + { + fe_timeout_remove (serv->joindelay_tag); + serv->joindelay_tag = 0; + } + +#ifdef USE_OPENSSL + if (serv->ssl) + { + _SSL_close (serv->ssl); + serv->ssl = NULL; + } +#endif + + if (serv->connecting) + { + server_stopconnecting (serv); + closesocket (serv->sok4); + if (serv->proxy_sok4 != -1) + closesocket (serv->proxy_sok4); + if (serv->sok6 != -1) + closesocket (serv->sok6); + if (serv->proxy_sok6 != -1) + closesocket (serv->proxy_sok6); + return 1; + } + + if (serv->connected) + { + close_socket (serv->sok); + if (serv->proxy_sok) + close_socket (serv->proxy_sok); + serv->connected = FALSE; + serv->end_of_motd = FALSE; + return 2; + } + + /* is this server in a reconnect delay? remove it! */ + if (serv->recondelay_tag) + { + fe_timeout_remove (serv->recondelay_tag); + serv->recondelay_tag = 0; + return 3; + } + + return 0; +} + +static void +server_disconnect (session * sess, int sendquit, int err) +{ + server *serv = sess->server; + GSList *list; + char tbuf[64]; + gboolean shutup = FALSE; + + /* send our QUIT reason */ + if (sendquit && serv->connected) + { + server_sendquit (sess); + } + + fe_server_event (serv, FE_SE_DISCONNECT, 0); + + /* close all sockets & io tags */ + switch (server_cleanup (serv)) + { + case 0: /* it wasn't even connected! */ + notc_msg (sess); + return; + case 1: /* it was in the process of connecting */ + sprintf (tbuf, "%d", sess->server->childpid); + EMIT_SIGNAL (XP_TE_STOPCONNECT, sess, tbuf, NULL, NULL, NULL, 0); + return; + case 3: + shutup = TRUE; /* won't print "disconnected" in channels */ + } + + server_flush_queue (serv); + + list = sess_list; + while (list) + { + sess = (struct session *) list->data; + if (sess->server == serv) + { + if (!shutup || sess->type == SESS_SERVER) + /* print "Disconnected" to each window using this server */ + EMIT_SIGNAL (XP_TE_DISCON, sess, errorstring (err), NULL, NULL, NULL, 0); + + if (!sess->channel[0] || sess->type == SESS_CHANNEL) + clear_channel (sess); + } + list = list->next; + } + + serv->pos = 0; + serv->motd_skipped = FALSE; + serv->no_login = FALSE; + serv->servername[0] = 0; + serv->lag_sent = 0; + + notify_cleanup (); +} + +/* send a "print text" command to the parent process - MUST END IN \n! */ + +static void +proxy_error (int fd, char *msg) +{ + write (fd, "0\n", 2); + write (fd, msg, strlen (msg)); +} + +struct sock_connect +{ + char version; + char type; + guint16 port; + guint32 address; + char username[10]; +}; + +/* traverse_socks() returns: + * 0 success * + * 1 socks traversal failed */ + +static int +traverse_socks (int print_fd, int sok, char *serverAddr, int port) +{ + struct sock_connect sc; + unsigned char buf[256]; + + sc.version = 4; + sc.type = 1; + sc.port = htons (port); + sc.address = inet_addr (serverAddr); + strncpy (sc.username, prefs.username, 9); + + send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0); + buf[1] = 0; + recv (sok, buf, 10, 0); + if (buf[1] == 90) + return 0; + + snprintf (buf, sizeof (buf), "SOCKS\tServer reported error %d,%d.\n", buf[0], buf[1]); + proxy_error (print_fd, buf); + return 1; +} + +struct sock5_connect1 +{ + char version; + char nmethods; + char method; +}; + +static int +traverse_socks5 (int print_fd, int sok, char *serverAddr, int port) +{ + struct sock5_connect1 sc1; + unsigned char *sc2; + unsigned int packetlen, addrlen; + unsigned char buf[260]; + int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0]; + + sc1.version = 5; + sc1.nmethods = 1; + if (auth) + sc1.method = 2; /* Username/Password Authentication (UPA) */ + else + sc1.method = 0; /* NO Authentication */ + send (sok, (char *) &sc1, 3, 0); + if (recv (sok, buf, 2, 0) != 2) + goto read_error; + + if (buf[0] != 5) + { + proxy_error (print_fd, "SOCKS\tServer is not socks version 5.\n"); + return 1; + } + + /* did the server say no auth required? */ + if (buf[1] == 0) + auth = 0; + + if (auth) + { + int len_u=0, len_p=0; + + /* authentication sub-negotiation (RFC1929) */ + if (buf[1] != 2) /* UPA not supported by server */ + { + proxy_error (print_fd, "SOCKS\tServer doesn't support UPA authentication.\n"); + return 1; + } + + memset (buf, 0, sizeof(buf)); + + /* form the UPA request */ + len_u = strlen (prefs.proxy_user); + len_p = strlen (prefs.proxy_pass); + buf[0] = 1; + buf[1] = len_u; + memcpy (buf + 2, prefs.proxy_user, len_u); + buf[2 + len_u] = len_p; + memcpy (buf + 3 + len_u, prefs.proxy_pass, len_p); + + send (sok, buf, 3 + len_u + len_p, 0); + if ( recv (sok, buf, 2, 0) != 2 ) + goto read_error; + if ( buf[1] != 0 ) + { + proxy_error (print_fd, "SOCKS\tAuthentication failed. " + "Is username and password correct?\n"); + return 1; /* UPA failed! */ + } + } + else + { + if (buf[1] != 0) + { + proxy_error (print_fd, "SOCKS\tAuthentication required but disabled in settings.\n"); + return 1; + } + } + + addrlen = strlen (serverAddr); + packetlen = 4 + 1 + addrlen + 2; + sc2 = malloc (packetlen); + sc2[0] = 5; /* version */ + sc2[1] = 1; /* command */ + sc2[2] = 0; /* reserved */ + sc2[3] = 3; /* address type */ + sc2[4] = (unsigned char) addrlen; /* hostname length */ + memcpy (sc2 + 5, serverAddr, addrlen); + *((unsigned short *) (sc2 + 5 + addrlen)) = htons (port); + send (sok, sc2, packetlen, 0); + free (sc2); + + /* consume all of the reply */ + if (recv (sok, buf, 4, 0) != 4) + goto read_error; + if (buf[0] != 5 || buf[1] != 0) + { + if (buf[1] == 2) + snprintf (buf, sizeof (buf), "SOCKS\tProxy refused to connect to host (not allowed).\n"); + else + snprintf (buf, sizeof (buf), "SOCKS\tProxy failed to connect to host (error %d).\n", buf[1]); + proxy_error (print_fd, buf); + return 1; + } + if (buf[3] == 1) /* IPV4 32bit address */ + { + if (recv (sok, buf, 6, 0) != 6) + goto read_error; + } else if (buf[3] == 4) /* IPV6 128bit address */ + { + if (recv (sok, buf, 18, 0) != 18) + goto read_error; + } else if (buf[3] == 3) /* string, 1st byte is size */ + { + if (recv (sok, buf, 1, 0) != 1) /* read the string size */ + goto read_error; + packetlen = buf[0] + 2; /* can't exceed 260 */ + if (recv (sok, buf, packetlen, 0) != packetlen) + goto read_error; + } + + return 0; /* success */ + +read_error: + proxy_error (print_fd, "SOCKS\tRead error from server.\n"); + return 1; +} + +static int +traverse_wingate (int print_fd, int sok, char *serverAddr, int port) +{ + char buf[128]; + + snprintf (buf, sizeof (buf), "%s %d\r\n", serverAddr, port); + send (sok, buf, strlen (buf), 0); + + return 0; +} + +/* stuff for HTTP auth is here */ + +static void +three_to_four (char *from, char *to) +{ + static const char tab64[64]= + { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + + to[0] = tab64 [ (from[0] >> 2) & 63 ]; + to[1] = tab64 [ ((from[0] << 4) | (from[1] >> 4)) & 63 ]; + to[2] = tab64 [ ((from[1] << 2) | (from[2] >> 6)) & 63 ]; + to[3] = tab64 [ from[2] & 63 ]; +}; + +void +base64_encode (char *to, char *from, unsigned int len) +{ + while (len >= 3) + { + three_to_four (from, to); + len -= 3; + from += 3; + to += 4; + } + if (len) + { + char three[3]={0,0,0}; + int i=0; + for (i=0;i<len;i++) + three[i] = *from++; + three_to_four (three, to); + if (len == 1) + to[2] = to[3] = '='; + else if (len == 2) + to[3] = '='; + to += 4; + }; + to[0] = 0; +} + +static int +http_read_line (int print_fd, int sok, char *buf, int len) +{ +#ifdef WIN32 + /* make sure waitline() uses recv() or it'll fail on win32 */ + len = waitline (sok, buf, len, FALSE); +#else + len = waitline (sok, buf, len, TRUE); +#endif + if (len >= 1) + { + /* print the message out (send it to the parent process) */ + write (print_fd, "0\n", 2); + + if (buf[len-1] == '\r') + { + buf[len-1] = '\n'; + write (print_fd, buf, len); + } else + { + write (print_fd, buf, len); + write (print_fd, "\n", 1); + } + } + + return len; +} + +static int +traverse_http (int print_fd, int sok, char *serverAddr, int port) +{ + char buf[512]; + char auth_data[256]; + char auth_data2[252]; + int n, n2; + + n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n", + serverAddr, port); + if (prefs.proxy_auth) + { + n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s", + prefs.proxy_user, prefs.proxy_pass); + base64_encode (auth_data, auth_data2, n2); + n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data); + } + n += snprintf (buf+n, sizeof (buf)-n, "\r\n"); + send (sok, buf, n, 0); + + n = http_read_line (print_fd, sok, buf, sizeof (buf)); + /* "HTTP/1.0 200 OK" */ + if (n < 12) + return 1; + if (memcmp (buf, "HTTP/", 5) || memcmp (buf + 9, "200", 3)) + return 1; + while (1) + { + /* read until blank line */ + n = http_read_line (print_fd, sok, buf, sizeof (buf)); + if (n < 1 || (n == 1 && buf[0] == '\n')) + break; + } + return 0; +} + +static int +traverse_proxy (int proxy_type, int print_fd, int sok, char *ip, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound) +{ + switch (proxy_type) + { + case 1: + return traverse_wingate (print_fd, sok, ip, port); + case 2: + return traverse_socks (print_fd, sok, ip, port); + case 3: + return traverse_socks5 (print_fd, sok, ip, port); + case 4: + return traverse_http (print_fd, sok, ip, port); +#ifdef USE_MSPROXY + case 5: + return traverse_msproxy (sok, ip, port, state, ns_proxy, csok4, csok6, csok, bound); +#endif + } + + return 1; +} + +/* this is the child process making the connection attempt */ + +static int +server_child (server * serv) +{ + netstore *ns_server; + netstore *ns_proxy = NULL; + netstore *ns_local; + int port = serv->port; + int error; + int sok, psok; + char *hostname = serv->hostname; + char *real_hostname = NULL; + char *ip; + char *proxy_ip = NULL; + char *local_ip; + int connect_port; + char buf[512]; + char bound = 0; + int proxy_type = 0; + char *proxy_host = NULL; + int proxy_port; + + ns_server = net_store_new (); + + /* is a hostname set? - bind to it */ + if (prefs.hostname[0]) + { + ns_local = net_store_new (); + local_ip = net_resolve (ns_local, prefs.hostname, 0, &real_hostname); + if (local_ip != NULL) + { + snprintf (buf, sizeof (buf), "5\n%s\n", local_ip); + write (serv->childwrite, buf, strlen (buf)); + net_bind (ns_local, serv->sok4, serv->sok6); + bound = 1; + } else + { + write (serv->childwrite, "7\n", 2); + } + net_store_destroy (ns_local); + } + + if (!serv->dont_use_proxy) /* blocked in serverlist? */ + { + if (FALSE) + ; +#ifdef USE_LIBPROXY + else if (prefs.proxy_type == 5) + { + char **proxy_list; + char *url, *proxy; + + url = g_strdup_printf ("irc://%s:%d", hostname, port); + proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url); + + if (proxy_list) { + /* can use only one */ + proxy = proxy_list[0]; + if (!strncmp (proxy, "direct", 6)) + proxy_type = 0; + else if (!strncmp (proxy, "http", 4)) + proxy_type = 4; + else if (!strncmp (proxy, "socks5", 6)) + proxy_type = 3; + else if (!strncmp (proxy, "socks", 5)) + proxy_type = 2; + } + + if (proxy_type) { + char *c; + c = strchr (proxy, ':') + 3; + proxy_host = strdup (c); + c = strchr (proxy_host, ':'); + *c = '\0'; + proxy_port = atoi (c + 1); + } + + g_strfreev (proxy_list); + g_free (url); +#endif + } else if (prefs.proxy_host[0] && + prefs.proxy_type > 0 && + prefs.proxy_use != 2) /* proxy is NOT dcc-only */ + { + proxy_type = prefs.proxy_type; + proxy_host = strdup (prefs.proxy_host); + proxy_port = prefs.proxy_port; + } + } + + serv->proxy_type = proxy_type; + + /* first resolve where we want to connect to */ + if (proxy_type > 0) + { + snprintf (buf, sizeof (buf), "9\n%s\n", proxy_host); + write (serv->childwrite, buf, strlen (buf)); + ip = net_resolve (ns_server, proxy_host, proxy_port, &real_hostname); + free (proxy_host); + if (!ip) + { + write (serv->childwrite, "1\n", 2); + goto xit; + } + connect_port = proxy_port; + + /* if using socks4 or MS Proxy, attempt to resolve ip for irc server */ + if ((proxy_type == 2) || (proxy_type == 5)) + { + ns_proxy = net_store_new (); + proxy_ip = net_resolve (ns_proxy, hostname, port, &real_hostname); + if (!proxy_ip) + { + write (serv->childwrite, "1\n", 2); + goto xit; + } + } else /* otherwise we can just use the hostname */ + proxy_ip = strdup (hostname); + } else + { + ip = net_resolve (ns_server, hostname, port, &real_hostname); + if (!ip) + { + write (serv->childwrite, "1\n", 2); + goto xit; + } + connect_port = port; + } + + snprintf (buf, sizeof (buf), "3\n%s\n%s\n%d\n", + real_hostname, ip, connect_port); + write (serv->childwrite, buf, strlen (buf)); + + if (!serv->dont_use_proxy && (proxy_type == 5)) + error = net_connect (ns_server, serv->proxy_sok4, serv->proxy_sok6, &psok); + else + { + error = net_connect (ns_server, serv->sok4, serv->sok6, &sok); + psok = sok; + } + + if (error != 0) + { + snprintf (buf, sizeof (buf), "2\n%d\n", sock_error ()); + write (serv->childwrite, buf, strlen (buf)); + } else + { + /* connect succeeded */ + if (proxy_ip) + { + switch (traverse_proxy (proxy_type, serv->childwrite, psok, proxy_ip, port, &serv->msp_state, ns_proxy, serv->sok4, serv->sok6, &sok, bound)) + { + case 0: /* success */ +#ifdef USE_MSPROXY + if (!serv->dont_use_proxy && (proxy_type == 5)) + snprintf (buf, sizeof (buf), "4\n%d %d %d %d %d\n", sok, psok, serv->msp_state.clientid, serv->msp_state.serverid, + serv->msp_state.seq_sent); + else +#endif + snprintf (buf, sizeof (buf), "4\n%d\n", sok); /* success */ + write (serv->childwrite, buf, strlen (buf)); + break; + case 1: /* socks traversal failed */ + write (serv->childwrite, "8\n", 2); + break; + } + } else + { + snprintf (buf, sizeof (buf), "4\n%d\n", sok); /* success */ + write (serv->childwrite, buf, strlen (buf)); + } + } + +xit: + +#if defined (USE_IPV6) || defined (WIN32) + /* this is probably not needed */ + net_store_destroy (ns_server); + if (ns_proxy) + net_store_destroy (ns_proxy); +#endif + + /* no need to free ip/real_hostname, this process is exiting */ +#ifdef WIN32 + /* under win32 we use a thread -> shared memory, must free! */ + if (proxy_ip) + free (proxy_ip); + if (ip) + free (ip); + if (real_hostname) + free (real_hostname); +#endif + + return 0; +} + +static void +server_connect (server *serv, char *hostname, int port, int no_login) +{ + int pid, read_des[2]; + session *sess = serv->server_session; + +#ifdef USE_OPENSSL + if (!ctx && serv->use_ssl) + { + if (!(ctx = _SSL_context_init (ssl_cb_info, FALSE))) + { + fprintf (stderr, "_SSL_context_init failed\n"); + exit (1); + } + } +#endif + + if (!hostname[0]) + return; + + if (port < 0) + { + /* use default port for this server type */ + port = 6667; +#ifdef USE_OPENSSL + if (serv->use_ssl) + port = 9999; +#endif + } + port &= 0xffff; /* wrap around */ + + if (serv->connected || serv->connecting || serv->recondelay_tag) + server_disconnect (sess, TRUE, -1); + + fe_progressbar_start (sess); + + EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, hostname, NULL, NULL, NULL, 0); + + safe_strcpy (serv->servername, hostname, sizeof (serv->servername)); + /* overlap illegal in strncpy */ + if (hostname != serv->hostname) + safe_strcpy (serv->hostname, hostname, sizeof (serv->hostname)); + +#ifdef USE_OPENSSL + if (serv->use_ssl) + { + char cert_file[256]; + + /* first try network specific cert/key */ + snprintf (cert_file, sizeof (cert_file), "%s/%s.pem", + get_xdir_fs (), server_get_network (serv, TRUE)); + if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) + SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM); + else + { + /* if that doesn't exist, try ~/.xchat2/client.pem */ + snprintf (cert_file, sizeof (cert_file), "%s/%s.pem", + get_xdir_fs (), "client"); + if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) + SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM); + } + } +#endif + + server_set_defaults (serv); + serv->connecting = TRUE; + serv->port = port; + serv->no_login = no_login; + + fe_server_event (serv, FE_SE_CONNECTING, 0); + fe_set_away (serv); + server_flush_queue (serv); + +#ifdef WIN32 + if (_pipe (read_des, 4096, _O_BINARY) < 0) +#else + if (pipe (read_des) < 0) +#endif + return; +#ifdef __EMX__ /* os/2 */ + setmode (read_des[0], O_BINARY); + setmode (read_des[1], O_BINARY); +#endif + serv->childread = read_des[0]; + serv->childwrite = read_des[1]; + + /* create both sockets now, drop one later */ + net_sockets (&serv->sok4, &serv->sok6); +#ifdef USE_MSPROXY + /* In case of MS Proxy we have a separate UDP control connection */ + if (!serv->dont_use_proxy && (serv->proxy_type == 5)) + udp_sockets (&serv->proxy_sok4, &serv->proxy_sok6); + else +#endif + { + serv->proxy_sok4 = -1; + serv->proxy_sok6 = -1; + } + +#ifdef WIN32 + CloseHandle (CreateThread (NULL, 0, + (LPTHREAD_START_ROUTINE)server_child, + serv, 0, (DWORD *)&pid)); +#else +#ifdef LOOKUPD + rand(); /* CL: net_resolve calls rand() when LOOKUPD is set, so prepare a different seed for each child. This method giver a bigger variation in seed values than calling srand(time(0)) in the child itself. */ +#endif + switch (pid = fork ()) + { + case -1: + return; + + case 0: + /* this is the child */ + setuid (getuid ()); + server_child (serv); + _exit (0); + } +#endif + serv->childpid = pid; + serv->iotag = fe_input_add (serv->childread, FIA_READ, server_read_child, + serv); +} + +void +server_fill_her_up (server *serv) +{ + serv->connect = server_connect; + serv->disconnect = server_disconnect; + serv->cleanup = server_cleanup; + serv->flush_queue = server_flush_queue; + serv->auto_reconnect = auto_reconnect; + + proto_fill_her_up (serv); +} + +void +server_set_encoding (server *serv, char *new_encoding) +{ + char *space; + + if (serv->encoding) + { + free (serv->encoding); + /* can be left as NULL to indicate system encoding */ + serv->encoding = NULL; + serv->using_cp1255 = FALSE; + serv->using_irc = FALSE; + } + + if (new_encoding) + { + serv->encoding = strdup (new_encoding); + /* the serverlist GUI might have added a space + and short description - remove it. */ + space = strchr (serv->encoding, ' '); + if (space) + space[0] = 0; + + /* server_inline() uses these flags */ + if (!strcasecmp (serv->encoding, "CP1255") || + !strcasecmp (serv->encoding, "WINDOWS-1255")) + serv->using_cp1255 = TRUE; + else if (!strcasecmp (serv->encoding, "IRC")) + serv->using_irc = TRUE; + } +} + +server * +server_new (void) +{ + static int id = 0; + server *serv; + + serv = malloc (sizeof (struct server)); + memset (serv, 0, sizeof (struct server)); + + /* use server.c and proto-irc.c functions */ + server_fill_her_up (serv); + + serv->id = id++; + serv->sok = -1; + strcpy (serv->nick, prefs.nick1); + server_set_defaults (serv); + + serv_list = g_slist_prepend (serv_list, serv); + + fe_new_server (serv); + + return serv; +} + +int +is_server (server *serv) +{ + return g_slist_find (serv_list, serv) ? 1 : 0; +} + +void +server_set_defaults (server *serv) +{ + if (serv->chantypes) + free (serv->chantypes); + if (serv->chanmodes) + free (serv->chanmodes); + if (serv->nick_prefixes) + free (serv->nick_prefixes); + if (serv->nick_modes) + free (serv->nick_modes); + + serv->chantypes = strdup ("#&!+"); + serv->chanmodes = strdup ("beI,k,l"); + serv->nick_prefixes = strdup ("@%+"); + serv->nick_modes = strdup ("ohv"); + + serv->nickcount = 1; + serv->nickservtype = 1; + serv->end_of_motd = FALSE; + serv->is_away = FALSE; + serv->supports_watch = FALSE; + serv->bad_prefix = FALSE; + serv->use_who = TRUE; + serv->have_namesx = FALSE; + serv->have_uhnames = FALSE; + serv->have_whox = FALSE; + serv->have_capab = FALSE; + serv->have_idmsg = FALSE; + serv->have_except = FALSE; +} + +char * +server_get_network (server *serv, gboolean fallback) +{ + if (serv->network) + return ((ircnet *)serv->network)->name; + + if (fallback) + return serv->servername; + + return NULL; +} + +void +server_set_name (server *serv, char *name) +{ + GSList *list = sess_list; + session *sess; + + if (name[0] == 0) + name = serv->hostname; + + /* strncpy parameters must NOT overlap */ + if (name != serv->servername) + { + safe_strcpy (serv->servername, name, sizeof (serv->servername)); + } + + while (list) + { + sess = (session *) list->data; + if (sess->server == serv) + fe_set_title (sess); + list = list->next; + } + + if (serv->server_session->type == SESS_SERVER) + { + if (serv->network) + { + safe_strcpy (serv->server_session->channel, ((ircnet *)serv->network)->name, CHANLEN); + } else + { + safe_strcpy (serv->server_session->channel, name, CHANLEN); + } + fe_set_channel (serv->server_session); + } +} + +struct away_msg * +server_away_find_message (server *serv, char *nick) +{ + struct away_msg *away; + GSList *list = away_list; + while (list) + { + away = (struct away_msg *) list->data; + if (away->server == serv && !serv->p_cmp (nick, away->nick)) + return away; + list = list->next; + } + return NULL; +} + +static void +server_away_free_messages (server *serv) +{ + GSList *list, *next; + struct away_msg *away; + + list = away_list; + while (list) + { + away = list->data; + next = list->next; + if (away->server == serv) + { + away_list = g_slist_remove (away_list, away); + if (away->message) + free (away->message); + free (away); + next = away_list; + } + list = next; + } +} + +void +server_away_save_message (server *serv, char *nick, char *msg) +{ + struct away_msg *away = server_away_find_message (serv, nick); + + if (away) /* Change message for known user */ + { + if (away->message) + free (away->message); + away->message = strdup (msg); + } else + /* Create brand new entry */ + { + away = malloc (sizeof (struct away_msg)); + if (away) + { + away->server = serv; + safe_strcpy (away->nick, nick, sizeof (away->nick)); + away->message = strdup (msg); + away_list = g_slist_prepend (away_list, away); + } + } +} + +void +server_free (server *serv) +{ + serv->cleanup (serv); + + serv_list = g_slist_remove (serv_list, serv); + + dcc_notify_kill (serv); + serv->flush_queue (serv); + server_away_free_messages (serv); + + free (serv->nick_modes); + free (serv->nick_prefixes); + free (serv->chanmodes); + free (serv->chantypes); + if (serv->bad_nick_prefixes) + free (serv->bad_nick_prefixes); + if (serv->last_away_reason) + free (serv->last_away_reason); + if (serv->encoding) + free (serv->encoding); + if (serv->autojoin) + free (serv->autojoin); + + fe_server_callback (serv); + + free (serv); + + notify_cleanup (); +} diff --git a/src/common/server.h b/src/common/server.h new file mode 100644 index 00000000..874d27b2 --- /dev/null +++ b/src/common/server.h @@ -0,0 +1,26 @@ +#ifndef XCHAT_SERVER_H +#define XCHAT_SERVER_H + +extern GSList *serv_list; + +/* eventually need to keep the tcp_* functions isolated to server.c */ +int tcp_send_len (server *serv, char *buf, int len); +int tcp_send (server *serv, char *buf); +void tcp_sendf (server *serv, char *fmt, ...); +int tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len); + +server *server_new (void); +int is_server (server *serv); +void server_fill_her_up (server *serv); +void server_set_encoding (server *serv, char *new_encoding); +void server_set_defaults (server *serv); +char *server_get_network (server *serv, gboolean fallback); +void server_set_name (server *serv, char *name); +void server_free (server *serv); + +void server_away_save_message (server *serv, char *nick, char *msg); +struct away_msg *server_away_find_message (server *serv, char *nick); + +void base64_encode (char *to, char *from, unsigned int len); + +#endif diff --git a/src/common/servlist.c b/src/common/servlist.c new file mode 100644 index 00000000..20a156a8 --- /dev/null +++ b/src/common/servlist.c @@ -0,0 +1,1311 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "xchat.h" +#include <glib/ghash.h> + +#include "cfgfiles.h" +#include "fe.h" +#include "server.h" +#include "text.h" +#include "util.h" /* token_foreach */ +#include "xchatc.h" + +#include "servlist.h" + + +struct defaultserver +{ + char *network; + char *host; + char *channel; + char *charset; +}; + +static const struct defaultserver def[] = +{ + {"2600net", 0}, + {0, "irc.2600.net"}, + + {"7-indonesia", 0}, + {0, "irc.7-indonesia.org"}, + + {"AccessIRC", 0}, + {0, "irc.accessirc.net"}, + {0, "eu.accessirc.net"}, + + {"AfterNET", 0}, + {0, "irc.afternet.org"}, + {0, "us.afternet.org"}, + {0, "eu.afternet.org"}, + + {"Aitvaras", 0}, +#ifdef USE_IPV6 +#ifdef USE_OPENSSL + {0, "irc6.ktu.lt/+7668"}, +#endif + {0, "irc6.ktu.lt/7666"}, +#endif +#ifdef USE_OPENSSL + {0, "irc.data.lt/+6668"}, + {0, "irc-ssl.omnitel.net/+6668"}, + {0, "irc-ssl.le.lt/+9999"}, +#endif + {0, "irc.data.lt"}, + {0, "irc.omnitel.net"}, + {0, "irc.ktu.lt"}, + {0, "irc.le.lt"}, + {0, "irc.takas.lt"}, + {0, "irc.5ci.net"}, + {0, "irc.kis.lt"}, + + {"AmigaNet", 0}, + {0, "irc.amiganet.org"}, + {0, "us.amiganet.org"}, + {0, "uk.amiganet.org"}, +/* {0, "no.amiganet.org"}, + {0, "au.amiganet.org"},*/ + + {"ARCNet", 0}, + {0, "se1.arcnet.vapor.com"}, + {0, "us1.arcnet.vapor.com"}, + {0, "us2.arcnet.vapor.com"}, + {0, "us3.arcnet.vapor.com"}, + {0, "ca1.arcnet.vapor.com"}, + {0, "de1.arcnet.vapor.com"}, + {0, "de3.arcnet.vapor.com"}, + {0, "ch1.arcnet.vapor.com"}, + {0, "be1.arcnet.vapor.com"}, + {0, "nl3.arcnet.vapor.com"}, + {0, "uk1.arcnet.vapor.com"}, + {0, "uk2.arcnet.vapor.com"}, +/* {0, "uk3.arcnet.vapor.com"},*/ + {0, "fr1.arcnet.vapor.com"}, + + {"AstroLink", 0}, + {0, "irc.astrolink.org"}, + + {"AustNet", 0}, + {0, "au.austnet.org"}, + {0, "us.austnet.org"}, + {0, "ca.austnet.org"}, + +/* {"AxeNet", 0}, + {0, "irc.axenet.org"}, + {0, "angel.axenet.org"}, + {0, "energy.axenet.org"}, + {0, "python.axenet.org"},*/ + + {"AzzurraNet", 0}, + {0, "irc.azzurra.org"}, + {0, "crypto.azzurra.org"}, + + {"Beirut", 0}, + {0, "irc.beirut.com"}, + + {"ChattingAway", 0}, + {0, "irc.chattingaway.com"}, + + {"ChatJunkies", 0, "#xchat"}, + {0, "irc.chatjunkies.org"}, + {0, "nl.chatjunkies.org"}, + + {"ChatNet", 0}, + {0, "US.ChatNet.Org"}, + {0, "EU.ChatNet.Org"}, + + {"ChatSociety", 0}, + {0, "us.chatsociety.net"}, + {0, "eu.chatsociety.net"}, + + {"ChatSpike", 0}, + {0, "irc.chatspike.net"}, + + {"CoolChat", 0}, + {0, "irc.coolchat.net"}, +/* {0, "unix.coolchat.net"}, + {0, "toronto.coolchat.net"},*/ + + {"Criten", 0}, + {0, "irc.criten.net"}, + {0, "irc.eu.criten.net"}, + + {"DALnet", 0}, + {0, "irc.dal.net"}, + {0, "irc.eu.dal.net"}, + + {"Dark-Tou-Net", 0}, + {0, "irc.d-t-net.de"}, + {0, "bw.d-t-net.de"}, + {0, "nc.d-t-net.de"}, + {0, "wakka.d-t-net.de"}, + + {"DarkMyst", 0}, + {0, "irc.darkmyst.org"}, + + {"DeepIRC", 0}, + {0, "irc.deepirc.net"}, + + {"DeltaAnime", 0}, + {0, "irc.deltaanime.net"}, + + {"EFnet", 0}, + {0, "irc.blackened.com"}, + {0, "irc.Prison.NET"}, + {0, "irc.Qeast.net"}, + {0, "irc.efnet.pl"}, + {0, "efnet.demon.co.uk"}, +/* {0, "irc.lagged.org"},*/ + {0, "irc.lightning.net"}, + {0, "irc.mindspring.com"}, + {0, "irc.easynews.com"}, + {0, "irc.servercentral.net"}, + + {"EnterTheGame", 0}, + {0, "IRC.EnterTheGame.Com"}, + + {"EUIrc", 0}, + {0, "irc.euirc.net"}, + {0, "irc.ham.de.euirc.net"}, + {0, "irc.ber.de.euirc.net"}, + {0, "irc.ffm.de.euirc.net"}, + {0, "irc.bre.de.euirc.net"}, + {0, "irc.hes.de.euirc.net"}, + {0, "irc.vie.at.euirc.net"}, + {0, "irc.inn.at.euirc.net"}, + {0, "irc.bas.ch.euirc.net"}, + + {"EuropNet", 0}, + {0, "irc.europnet.org"}, + + {"EU-IRC", 0}, + {0, "irc.eu-irc.net"}, + + {"FDFNet", 0}, + {0, "irc.fdfnet.net"}, + {0, "irc.eu.fdfnet.net"}, + + {"FEFNet", 0}, + {0, "irc.fef.net"}, + {0, "irc.ggn.net"}, + {0, "irc.vendetta.com"}, + + {"FreeNode", 0}, + {0, "irc.freenode.net"}, + +/* {"Freeworld", 0}, + {0, "kabel.freeworld.nu"}, + {0, "irc.freeworld.nu"},*/ + + {"GalaxyNet", 0}, + {0, "irc.galaxynet.org"}, +/* {0, "sprynet.us.galaxynet.org"}, + {0, "atlanta.ga.us.galaxynet.org"},*/ + + {"GamesNET", 0}, + {0, "irc.gamesnet.net"}, +/* {0, "irc.us.gamesnet.net"}, + {0, "east.us.gamesnet.net"}, + {0, "west.us.gamesnet.net"},*/ + {0, "irc.ca.gamesnet.net"}, + {0, "irc.eu.gamesnet.net"}, + + {"GeekShed", 0}, + {0, "irc.geekshed.net"}, + + {"German-Elite", 0}, + {0, "dominion.german-elite.net"}, + {0, "komatu.german-elite.net"}, +/* {0, "liberty.german-elite.net"},*/ + + {"GimpNet", 0}, + {0, "irc.gimp.org"}, +/* {0, "irc.au.gimp.org"},*/ + {0, "irc.us.gimp.org"}, + + {"HabberNet", 0}, + {0, "irc.habber.net"}, + + {"Hashmark", 0}, + {0, "irc.hashmark.net"}, + + {"IdleMonkeys", 0}, + {0, "irc.idlemonkeys.net"}, + +/* {"Infinity-IRC", 0}, + {0, "Atlanta.GA.US.Infinity-IRC.Org"}, + {0, "Babylon.NY.US.Infinity-IRC.Org"}, + {0, "Sunshine.Ca.US.Infinity-IRC.Org"}, + {0, "IRC.Infinity-IRC.Org"},*/ + + {"iZ-smart.net", 0}, + {0, "irc.iZ-smart.net/6666"}, + {0, "irc.iZ-smart.net/6667"}, + {0, "irc.iZ-smart.net/6668"}, + + {"IrcLink", 0}, + {0, "irc.irclink.net"}, + {0, "Alesund.no.eu.irclink.net"}, + {0, "Oslo.no.eu.irclink.net"}, + {0, "frogn.no.eu.irclink.net"}, + {0, "tonsberg.no.eu.irclink.net"}, + + {"IRCNet", 0}, + {0, "irc.ircnet.com"}, + {0, "irc.stealth.net/6668"}, + {0, "ircnet.demon.co.uk"}, +/* {0, "ircnet.hinet.hr"},*/ + {0, "irc.datacomm.ch"}, +/* {0, "ircnet.kaptech.fr"}, + {0, "ircnet.easynet.co.uk"},*/ + {0, "random.ircd.de"}, + {0, "ircnet.netvision.net.il"}, +/* {0, "irc.seed.net.tw"},*/ + {0, "irc.cs.hut.fi"}, + + {"Irctoo.net", 0}, + {0, "irc.irctoo.net"}, + + {"Krstarica", 0}, + {0, "irc.krstarica.com"}, + + {"Librenet", 0}, + {0, "irc.librenet.net"}, + {0, "ielf.fr.librenet.net"}, + + {"LinkNet", 0}, + {0, "irc.link-net.org"}, + {0, "irc.no.link-net.org"}, +/* {0, "irc.gamesden.net.au"},*/ + {0, "irc.bahnhof.se"}, +/* {0, "irc.kinexuseurope.co.uk"}, + {0, "irc.gamiix.com"},*/ + + {"MagicStar", 0}, + {0, "irc.magicstar.net"}, + + {"Majistic", 0}, + {0, "irc.majistic.net"}, + + {"MindForge", 0}, + {0, "irc.mindforge.org"}, + + {"MintIRC", 0}, + {0, "irc.mintirc.net"}, + + {"MIXXnet", 0}, + {0, "irc.mixxnet.net"}, + + {"NeverNET", 0}, + {0, "irc.nevernet.net"}, + {0, "imagine.nevernet.net"}, + {0, "dimension.nevernet.net"}, + {0, "universe.nevernet.net"}, + {0, "wayland.nevernet.net"}, + {0, "forte.nevernet.net"}, + + {"NixHelpNet", 0}, + {0, "irc.nixhelp.org"}, + {0, "us.nixhelp.org"}, + {0, "uk.nixhelp.org"}, + {0, "uk2.nixhelp.org"}, + {0, "uk3.nixhelp.org"}, + {0, "nl.nixhelp.org"}, + {0, "ca.ld.nixhelp.org"}, + {0, "us.co.nixhelp.org"}, + {0, "us.ca.nixhelp.org"}, + {0, "us.pa.nixhelp.org"}, + + {"NullusNet", 0}, + {0, "irc.nullus.net"}, + + {"Oceanius", 0}, + {0, "irc.oceanius.com"}, + + {"OFTC", 0}, + {0, "irc.oftc.net"}, + + {"OtherNet", 0}, + {0, "irc.othernet.org"}, + + {"OzNet", 0}, + {0, "irc.oz.org"}, + + {"PIRC.PL", 0}, + {0, "irc.pirc.pl"}, + + {"PTlink", 0}, + {0, "irc.PTlink.net"}, + {0, "aaia.PTlink.net"}, + + {"PTNet, ISP's", 0}, + {0, "irc.PTNet.org"}, + {0, "rccn.PTnet.org"}, + {0, "EUnet.PTnet.org"}, + {0, "madinfo.PTnet.org"}, + {0, "netc2.PTnet.org"}, + {0, "netc1.PTnet.org"}, + {0, "telepac1.ptnet.org"}, + {0, "esoterica.PTnet.org"}, + {0, "ip-hub.ptnet.org"}, + {0, "telepac1.ptnet.org"}, + {0, "nortenet.PTnet.org"}, + + {"PTNet, UNI", 0}, + {0, "irc.PTNet.org"}, + {0, "rccn.PTnet.org"}, + {0, "uevora.PTnet.org"}, + {0, "umoderna.PTnet.org"}, + {0, "ist.PTnet.org"}, + {0, "aaum.PTnet.org"}, + {0, "uc.PTnet.org"}, + {0, "ualg.ptnet.org"}, + {0, "madinfo.PTnet.org"}, +/* {0, "isep.PTnet.org"},*/ + {0, "ua.PTnet.org"}, + {0, "ipg.PTnet.org"}, + {0, "isec.PTnet.org"}, + {0, "utad.PTnet.org"}, + {0, "iscte.PTnet.org"}, + {0, "ubi.PTnet.org"}, + + {"QuakeNet", 0}, + {0, "irc.quakenet.org"}, + {0, "irc.se.quakenet.org"}, + {0, "irc.dk.quakenet.org"}, + {0, "irc.no.quakenet.org"}, + {0, "irc.fi.quakenet.org"}, + {0, "irc.be.quakenet.org"}, + {0, "irc.uk.quakenet.org"}, + {0, "irc.de.quakenet.org"}, + {0, "irc.it.quakenet.org"}, + + {"RebelChat", 0}, + {0, "irc.rebelchat.org"}, + +/* {"Recycled-IRC", 0}, + {0, "irc.recycled-irc.org"}, + {0, "vermin.recycled-irc.org"}, + {0, "waste.recycled-irc.org"}, + {0, "lumber.recycled-irc.org"}, + {0, "trash.recycled-irc.org"}, + {0, "unwashed.recycled-irc.org"}, + {0, "garbage.recycled-irc.org"}, + {0, "dustbin.recycled-irc.org"},*/ + + {"RizeNET", 0}, + {0, "irc.rizenet.org"}, + {0, "omega.rizenet.org"}, + {0, "evelance.rizenet.org"}, + {0, "lisa.rizenet.org"}, + {0, "scott.rizenet.org"}, + + {"Rizon", 0}, + {0, "irc.rizon.net"}, + + {"RusNet", 0, 0, "KOI8-R (Cyrillic)"}, + {0, "irc.tomsk.net"}, + {0, "irc.rinet.ru"}, + {0, "irc.run.net"}, + {0, "irc.ru"}, + {0, "irc.lucky.net"}, + + {"SceneNet", 0}, + {0, "irc.scene.org"}, + {0, "irc.eu.scene.org"}, + {0, "irc.us.scene.org"}, + + {"SeilEn.de", 0}, + {0, "irc.seilen.de"}, + + {"SlashNET", 0}, + {0, "irc.slashnet.org"}, + {0, "area51.slashnet.org"}, + {0, "moo.slashnet.org"}, + {0, "radon.slashnet.org"}, + + {"Sohbet.Net", 0}, + {0, "irc.sohbet.net"}, + + {"SolidIRC", 0}, + {0, "irc.solidirc.com"}, + + {"SorceryNet", 0}, + {0, "irc.sorcery.net/9000"}, + {0, "irc.us.sorcery.net/9000"}, + {0, "irc.eu.sorcery.net/9000"}, + + {"Spidernet", 0}, + {0, "us.spidernet.org"}, + {0, "eu.spidernet.org"}, + {0, "irc.spidernet.org"}, + + {"StarChat", 0}, + {0, "irc.starchat.net"}, + {0, "gainesville.starchat.net"}, + {0, "freebsd.starchat.net"}, + {0, "sunset.starchat.net"}, + {0, "revenge.starchat.net"}, + {0, "tahoma.starchat.net"}, + {0, "neo.starchat.net"}, + + {"TNI3", 0}, + {0, "irc.tni3.com"}, + + {"TURLINet", 0}, + {0, "irc.turli.net"}, + {0, "irc.servx.ru"}, + {0, "irc.gavnos.ru"}, + + {"UnderNet", 0}, + {0, "us.undernet.org"}, + {0, "eu.undernet.org"}, + + {"UniBG", 0}, + {0, "irc.lirex.com"}, + {0, "irc.naturella.com"}, + {0, "irc.spnet.net"}, + {0, "irc.techno-link.com"}, + {0, "irc.telecoms.bg"}, + {0, "irc.tu-varna.edu"}, + + {"Whiffle", 0}, + {0, "irc.whiffle.org"}, + + {"Worldnet", 0}, + {0, "irc.worldnet.net"}, + {0, "irc.fr.worldnet.net"}, + + {"Xentonix.net", 0}, + {0, "irc.xentonix.net"}, + + {"XWorld", 0}, + {0, "Buffalo.NY.US.XWorld.org"}, + {0, "Minneapolis.MN.US.Xworld.Org"}, + {0, "Rochester.NY.US.XWorld.org"}, + {0, "Bayern.DE.EU.XWorld.Org"}, + {0, "Chicago.IL.US.XWorld.Org"}, + + {0,0} +}; + +GSList *network_list = 0; + + +void +servlist_connect (session *sess, ircnet *net, gboolean join) +{ + ircserver *ircserv; + GSList *list; + char *port; + server *serv; + + if (!sess) + sess = new_ircwindow (NULL, NULL, SESS_SERVER, TRUE); + + serv = sess->server; + + /* connect to the currently selected Server-row */ + list = g_slist_nth (net->servlist, net->selected); + if (!list) + list = net->servlist; + if (!list) + return; + ircserv = list->data; + + /* incase a protocol switch is added to the servlist gui */ + server_fill_her_up (sess->server); + + if (join) + { + sess->willjoinchannel[0] = 0; + + if (net->autojoin) + { + if (serv->autojoin) + free (serv->autojoin); + serv->autojoin = strdup (net->autojoin); + } + } + + serv->password[0] = 0; + if (net->pass) + safe_strcpy (serv->password, net->pass, sizeof (serv->password)); + + if (net->flags & FLAG_USE_GLOBAL) + { + strcpy (serv->nick, prefs.nick1); + } else + { + if (net->nick) + strcpy (serv->nick, net->nick); + } + + serv->dont_use_proxy = (net->flags & FLAG_USE_PROXY) ? FALSE : TRUE; + +#ifdef USE_OPENSSL + serv->use_ssl = (net->flags & FLAG_USE_SSL) ? TRUE : FALSE; + serv->accept_invalid_cert = + (net->flags & FLAG_ALLOW_INVALID) ? TRUE : FALSE; +#endif + + serv->network = net; + + port = strrchr (ircserv->hostname, '/'); + if (port) + { + *port = 0; + + /* support "+port" to indicate SSL (like mIRC does) */ + if (port[1] == '+') + { +#ifdef USE_OPENSSL + serv->use_ssl = TRUE; +#endif + serv->connect (serv, ircserv->hostname, atoi (port + 2), FALSE); + } else + { + serv->connect (serv, ircserv->hostname, atoi (port + 1), FALSE); + } + + *port = '/'; + } else + serv->connect (serv, ircserv->hostname, -1, FALSE); + + server_set_encoding (serv, net->encoding); +} + +int +servlist_connect_by_netname (session *sess, char *network, gboolean join) +{ + ircnet *net; + GSList *list = network_list; + + while (list) + { + net = list->data; + + if (strcasecmp (net->name, network) == 0) + { + servlist_connect (sess, net, join); + return 1; + } + + list = list->next; + } + + return 0; +} + +int +servlist_have_auto (void) +{ + GSList *list = network_list; + ircnet *net; + + while (list) + { + net = list->data; + + if (net->flags & FLAG_AUTO_CONNECT) + return 1; + + list = list->next; + } + + return 0; +} + +int +servlist_auto_connect (session *sess) +{ + GSList *list = network_list; + ircnet *net; + int ret = 0; + + while (list) + { + net = list->data; + + if (net->flags & FLAG_AUTO_CONNECT) + { + servlist_connect (sess, net, TRUE); + ret = 1; + } + + list = list->next; + } + + return ret; +} + +static gint +servlist_cycle_cb (server *serv) +{ + if (serv->network) + { + PrintTextf (serv->server_session, + _("Cycling to next server in %s...\n"), ((ircnet *)serv->network)->name); + servlist_connect (serv->server_session, serv->network, TRUE); + } + + return 0; +} + +int +servlist_cycle (server *serv) +{ + ircnet *net; + int max, del; + + net = serv->network; + if (net) + { + max = g_slist_length (net->servlist); + if (max > 0) + { + /* try the next server, if that option is on */ + if (net->flags & FLAG_CYCLE) + { + net->selected++; + if (net->selected >= max) + net->selected = 0; + } + + del = prefs.recon_delay * 1000; + if (del < 1000) + del = 500; /* so it doesn't block the gui */ + + if (del) + serv->recondelay_tag = fe_timeout_add (del, servlist_cycle_cb, serv); + else + servlist_connect (serv->server_session, net, TRUE); + + return TRUE; + } + } + + return FALSE; +} + +ircserver * +servlist_server_find (ircnet *net, char *name, int *pos) +{ + GSList *list = net->servlist; + ircserver *serv; + int i = 0; + + while (list) + { + serv = list->data; + if (strcmp (serv->hostname, name) == 0) + { + if (pos) + *pos = i; + return serv; + } + i++; + list = list->next; + } + + return NULL; +} + +/* find a network (e.g. (ircnet *) to "FreeNode") from a hostname + (e.g. "irc.eu.freenode.net") */ + +ircnet * +servlist_net_find_from_server (char *server_name) +{ + GSList *list = network_list; + GSList *slist; + ircnet *net; + ircserver *serv; + + while (list) + { + net = list->data; + + slist = net->servlist; + while (slist) + { + serv = slist->data; + if (strcasecmp (serv->hostname, server_name) == 0) + return net; + slist = slist->next; + } + + list = list->next; + } + + return NULL; +} + +ircnet * +servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *)) +{ + GSList *list = network_list; + ircnet *net; + int i = 0; + + while (list) + { + net = list->data; + if (cmpfunc (net->name, name) == 0) + { + if (pos) + *pos = i; + return net; + } + i++; + list = list->next; + } + + return NULL; +} + +ircserver * +servlist_server_add (ircnet *net, char *name) +{ + ircserver *serv; + + serv = malloc (sizeof (ircserver)); + memset (serv, 0, sizeof (ircserver)); + serv->hostname = strdup (name); + + net->servlist = g_slist_append (net->servlist, serv); + + return serv; +} + +void +servlist_server_remove (ircnet *net, ircserver *serv) +{ + free (serv->hostname); + free (serv); + net->servlist = g_slist_remove (net->servlist, serv); +} + +static void +servlist_server_remove_all (ircnet *net) +{ + ircserver *serv; + + while (net->servlist) + { + serv = net->servlist->data; + servlist_server_remove (net, serv); + } +} + +static void +free_and_clear (char *str) +{ + if (str) + { + char *orig = str; + while (*str) + *str++ = 0; + free (orig); + } +} + +/* executed on exit: Clear any password strings */ + +void +servlist_cleanup (void) +{ + GSList *list; + ircnet *net; + + for (list = network_list; list; list = list->next) + { + net = list->data; + free_and_clear (net->pass); + free_and_clear (net->nickserv); + } +} + +void +servlist_net_remove (ircnet *net) +{ + GSList *list; + server *serv; + + servlist_server_remove_all (net); + network_list = g_slist_remove (network_list, net); + + if (net->nick) + free (net->nick); + if (net->nick2) + free (net->nick2); + if (net->user) + free (net->user); + if (net->real) + free (net->real); + free_and_clear (net->pass); + if (net->autojoin) + free (net->autojoin); + if (net->command) + free (net->command); + free_and_clear (net->nickserv); + if (net->comment) + free (net->comment); + if (net->encoding) + free (net->encoding); + free (net->name); + free (net); + + /* for safety */ + list = serv_list; + while (list) + { + serv = list->data; + if (serv->network == net) + serv->network = NULL; + list = list->next; + } +} + +ircnet * +servlist_net_add (char *name, char *comment, int prepend) +{ + ircnet *net; + + net = malloc (sizeof (ircnet)); + memset (net, 0, sizeof (ircnet)); + net->name = strdup (name); +/* net->comment = strdup (comment);*/ + net->flags = FLAG_CYCLE | FLAG_USE_GLOBAL | FLAG_USE_PROXY; + + if (prepend) + network_list = g_slist_prepend (network_list, net); + else + network_list = g_slist_append (network_list, net); + + return net; +} + +static void +servlist_load_defaults (void) +{ + int i = 0, j = 0; + ircnet *net = NULL; + + while (1) + { + if (def[i].network) + { + net = servlist_net_add (def[i].network, def[i].host, FALSE); + net->encoding = strdup ("IRC (Latin/Unicode Hybrid)"); + if (def[i].channel) + net->autojoin = strdup (def[i].channel); + if (def[i].charset) + { + free (net->encoding); + net->encoding = strdup (def[i].charset); + } + if (g_str_hash (def[i].network) == 0x8e1b96f7) + prefs.slist_select = j; + j++; + } else + { + servlist_server_add (net, def[i].host); + if (!def[i+1].host && !def[i+1].network) + break; + } + i++; + } +} + +static int +servlist_load (void) +{ + FILE *fp; + char buf[2048]; + int len; + char *tmp; + ircnet *net = NULL; + + fp = xchat_fopen_file ("servlist_.conf", "r", 0); + if (!fp) + return FALSE; + + while (fgets (buf, sizeof (buf) - 2, fp)) + { + len = strlen (buf); + buf[len] = 0; + buf[len-1] = 0; /* remove the trailing \n */ + if (net) + { + switch (buf[0]) + { + case 'I': + net->nick = strdup (buf + 2); + break; + case 'i': + net->nick2 = strdup (buf + 2); + break; + case 'U': + net->user = strdup (buf + 2); + break; + case 'R': + net->real = strdup (buf + 2); + break; + case 'P': + net->pass = strdup (buf + 2); + break; + case 'J': + net->autojoin = strdup (buf + 2); + break; + case 'C': + if (net->command) + { + /* concat extra commands with a \n separator */ + tmp = net->command; + net->command = malloc (strlen (tmp) + strlen (buf + 2) + 2); + strcpy (net->command, tmp); + strcat (net->command, "\n"); + strcat (net->command, buf + 2); + free (tmp); + } else + net->command = strdup (buf + 2); + break; + case 'F': + net->flags = atoi (buf + 2); + break; + case 'D': + net->selected = atoi (buf + 2); + break; + case 'E': + net->encoding = strdup (buf + 2); + break; + case 'S': /* new server/hostname for this network */ + servlist_server_add (net, buf + 2); + break; + case 'B': + net->nickserv = strdup (buf + 2); + break; + } + } + if (buf[0] == 'N') + net = servlist_net_add (buf + 2, /* comment */ NULL, FALSE); + } + fclose (fp); + + return TRUE; +} + +void +servlist_init (void) +{ + if (!network_list) + if (!servlist_load ()) + servlist_load_defaults (); +} + +/* check if a charset is known by Iconv */ +int +servlist_check_encoding (char *charset) +{ + GIConv gic; + char *c; + + c = strchr (charset, ' '); + if (c) + c[0] = 0; + + if (!strcasecmp (charset, "IRC")) /* special case */ + { + if (c) + c[0] = ' '; + return TRUE; + } + + gic = g_iconv_open (charset, "UTF-8"); + + if (c) + c[0] = ' '; + + if (gic != (GIConv)-1) + { + g_iconv_close (gic); + return TRUE; + } + + return FALSE; +} + +static int +servlist_write_ccmd (char *str, void *fp) +{ + return fprintf (fp, "C=%s\n", (str[0] == '/') ? str + 1 : str); +} + + +int +servlist_save (void) +{ + FILE *fp; + char buf[256]; + ircnet *net; + ircserver *serv; + GSList *list; + GSList *hlist; +#ifndef WIN32 + int first = FALSE; + + snprintf (buf, sizeof (buf), "%s/servlist_.conf", get_xdir_fs ()); + if (access (buf, F_OK) != 0) + first = TRUE; +#endif + + fp = xchat_fopen_file ("servlist_.conf", "w", 0); + if (!fp) + return FALSE; + +#ifndef WIN32 + if (first) + chmod (buf, 0600); +#endif + fprintf (fp, "v="PACKAGE_VERSION"\n\n"); + + list = network_list; + while (list) + { + net = list->data; + + fprintf (fp, "N=%s\n", net->name); + if (net->nick) + fprintf (fp, "I=%s\n", net->nick); + if (net->nick2) + fprintf (fp, "i=%s\n", net->nick2); + if (net->user) + fprintf (fp, "U=%s\n", net->user); + if (net->real) + fprintf (fp, "R=%s\n", net->real); + if (net->pass) + fprintf (fp, "P=%s\n", net->pass); + if (net->autojoin) + fprintf (fp, "J=%s\n", net->autojoin); + if (net->nickserv) + fprintf (fp, "B=%s\n", net->nickserv); + if (net->encoding && strcasecmp (net->encoding, "System") && + strcasecmp (net->encoding, "System default")) + { + fprintf (fp, "E=%s\n", net->encoding); + if (!servlist_check_encoding (net->encoding)) + { + snprintf (buf, sizeof (buf), _("Warning: \"%s\" character set is unknown. No conversion will be applied for network %s."), + net->encoding, net->name); + fe_message (buf, FE_MSG_WARN); + } + } + + if (net->command) + token_foreach (net->command, '\n', servlist_write_ccmd, fp); + + fprintf (fp, "F=%d\nD=%d\n", net->flags, net->selected); + + hlist = net->servlist; + while (hlist) + { + serv = hlist->data; + fprintf (fp, "S=%s\n", serv->hostname); + hlist = hlist->next; + } + + if (fprintf (fp, "\n") < 1) + { + fclose (fp); + return FALSE; + } + + list = list->next; + } + + fclose (fp); + return TRUE; +} + +static void +joinlist_free1 (GSList *list) +{ + GSList *head = list; + + for (; list; list = list->next) + g_free (list->data); + g_slist_free (head); +} + +void +joinlist_free (GSList *channels, GSList *keys) +{ + joinlist_free1 (channels); + joinlist_free1 (keys); +} + +gboolean +joinlist_is_in_list (server *serv, char *channel) +{ + GSList *channels, *keys; + GSList *list; + + if (!serv->network || !((ircnet *)serv->network)->autojoin) + return FALSE; + + joinlist_split (((ircnet *)serv->network)->autojoin, &channels, &keys); + + for (list = channels; list; list = list->next) + { + if (serv->p_cmp (list->data, channel) == 0) + return TRUE; + } + + joinlist_free (channels, keys); + + return FALSE; +} + +gchar * +joinlist_merge (GSList *channels, GSList *keys) +{ + GString *out = g_string_new (NULL); + GSList *list; + int i, j; + + for (; channels; channels = channels->next) + { + g_string_append (out, channels->data); + + if (channels->next) + g_string_append_c (out, ','); + } + + /* count number of REAL keys */ + for (i = 0, list = keys; list; list = list->next) + if (list->data) + i++; + + if (i > 0) + { + g_string_append_c (out, ' '); + + for (j = 0; keys; keys = keys->next) + { + if (keys->data) + { + g_string_append (out, keys->data); + j++; + if (j == i) + break; + } + + if (keys->next) + g_string_append_c (out, ','); + } + } + + return g_string_free (out, FALSE); +} + +void +joinlist_split (char *autojoin, GSList **channels, GSList **keys) +{ + char *parta, *partb; + char *chan, *key; + int len; + + *channels = NULL; + *keys = NULL; + + /* after the first space, the keys begin */ + parta = autojoin; + partb = strchr (autojoin, ' '); + if (partb) + partb++; + + while (1) + { + chan = parta; + key = partb; + + if (1) + { + while (parta[0] != 0 && parta[0] != ',' && parta[0] != ' ') + { + parta++; + } + } + + if (partb) + { + while (partb[0] != 0 && partb[0] != ',' && partb[0] != ' ') + { + partb++; + } + } + + len = parta - chan; + if (len < 1) + break; + *channels = g_slist_append (*channels, g_strndup (chan, len)); + + len = partb - key; + *keys = g_slist_append (*keys, len ? g_strndup (key, len) : NULL); + + if (parta[0] == ' ' || parta[0] == 0) + break; + parta++; + + if (partb) + { + if (partb[0] == 0 || partb[0] == ' ') + partb = NULL; /* no more keys, but maybe more channels? */ + else + partb++; + } + } + +#if 0 + GSList *lista, *listb; + int i; + + printf("-----\n"); + i = 0; + lista = *channels; + listb = *keys; + while (lista) + { + printf("%d. |%s| |%s|\n", i, lista->data, listb->data); + i++; + lista = lista->next; + listb = listb->next; + } + printf("-----\n\n"); +#endif +} + + diff --git a/src/common/servlist.h b/src/common/servlist.h new file mode 100644 index 00000000..9b7734f4 --- /dev/null +++ b/src/common/servlist.h @@ -0,0 +1,62 @@ +#ifndef XCHAT_SERVLIST_H +#define XCHAT_SERVLIST_H + +typedef struct ircserver +{ + char *hostname; +} ircserver; + +typedef struct ircnet +{ + char *name; + char *nick; + char *nick2; + char *user; + char *real; + char *pass; + char *autojoin; + char *command; + char *nickserv; + char *comment; + char *encoding; + GSList *servlist; + int selected; + guint32 flags; +} ircnet; + +extern GSList *network_list; + +#define FLAG_CYCLE 1 +#define FLAG_USE_GLOBAL 2 +#define FLAG_USE_SSL 4 +#define FLAG_AUTO_CONNECT 8 +#define FLAG_USE_PROXY 16 +#define FLAG_ALLOW_INVALID 32 +#define FLAG_FAVORITE 64 +#define FLAG_COUNT 7 + +void servlist_init (void); +int servlist_save (void); +int servlist_cycle (server *serv); +void servlist_connect (session *sess, ircnet *net, gboolean join); +int servlist_connect_by_netname (session *sess, char *network, gboolean join); +int servlist_auto_connect (session *sess); +int servlist_have_auto (void); +int servlist_check_encoding (char *charset); +void servlist_cleanup (void); + +ircnet *servlist_net_add (char *name, char *comment, int prepend); +void servlist_net_remove (ircnet *net); +ircnet *servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *)); +ircnet *servlist_net_find_from_server (char *server_name); + +void servlist_server_remove (ircnet *net, ircserver *serv); +ircserver *servlist_server_add (ircnet *net, char *name); +ircserver *servlist_server_find (ircnet *net, char *name, int *pos); + +void joinlist_split (char *autojoin, GSList **channels, GSList **keys); +gboolean joinlist_is_in_list (server *serv, char *channel); +void joinlist_free (GSList *channels, GSList *keys); +gchar *joinlist_merge (GSList *channels, GSList *keys); + +#endif diff --git a/src/common/ssl.c b/src/common/ssl.c new file mode 100644 index 00000000..a18ad47c --- /dev/null +++ b/src/common/ssl.c @@ -0,0 +1,323 @@ +/* + * ssl.c v0.0.3 + * Copyright (C) 2000 -- DaP <profeta@freemail.c3.hu> + * + * 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 <openssl/ssl.h> /* SSL_() */ +#include <openssl/err.h> /* ERR_() */ +#include <time.h> /* asctime() */ +#include <string.h> /* strncpy() */ +#include "ssl.h" /* struct cert_info */ +#include "inet.h" +#include "../../config.h" /* HAVE_SNPRINTF */ + +#ifndef HAVE_SNPRINTF +#define snprintf g_snprintf +#endif + +/* globals */ +static struct chiper_info chiper_info; /* static buffer for _SSL_get_cipher_info() */ +static char err_buf[256]; /* generic error buffer */ + + +/* +++++ Internal functions +++++ */ + +static void +__SSL_fill_err_buf (char *funcname) +{ + int err; + char buf[256]; + + + err = ERR_get_error (); + ERR_error_string (err, buf); + snprintf (err_buf, sizeof (err_buf), "%s: %s (%d)\n", funcname, buf, err); +} + + +static void +__SSL_critical_error (char *funcname) +{ + __SSL_fill_err_buf (funcname); + fprintf (stderr, "%s\n", err_buf); + + exit (1); +} + +/* +++++ SSL functions +++++ */ + +SSL_CTX * +_SSL_context_init (void (*info_cb_func), int server) +{ + SSL_CTX *ctx; +#ifdef WIN32 + int i, r; +#endif + + SSLeay_add_ssl_algorithms (); + SSL_load_error_strings (); + ctx = SSL_CTX_new (server ? SSLv3_server_method() : SSLv3_client_method ()); + + SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH); + SSL_CTX_set_timeout (ctx, 300); + + /* used in SSL_connect(), SSL_accept() */ + SSL_CTX_set_info_callback (ctx, info_cb_func); + +#ifdef WIN32 + /* under win32, OpenSSL needs to be seeded with some randomness */ + for (i = 0; i < 128; i++) + { + r = rand (); + RAND_seed ((unsigned char *)&r, sizeof (r)); + } +#endif + + return(ctx); +} + +static void +ASN1_TIME_snprintf (char *buf, int buf_len, ASN1_TIME * tm) +{ + char *expires = NULL; + BIO *inMem = BIO_new (BIO_s_mem ()); + + ASN1_TIME_print (inMem, tm); + BIO_get_mem_data (inMem, &expires); + buf[0] = 0; + if (expires != NULL) + { + memset (buf, 0, buf_len); + strncpy (buf, expires, 24); + } + BIO_free (inMem); +} + + +static void +broke_oneline (char *oneline, char *parray[]) +{ + char *pt, *ppt; + int i; + + + i = 0; + ppt = pt = oneline + 1; + while ((pt = strchr (pt, '/'))) + { + *pt = 0; + parray[i++] = ppt; + ppt = ++pt; + } + parray[i++] = ppt; + parray[i] = NULL; +} + + +/* + FIXME: Master-Key, Extensions, CA bits + (openssl x509 -text -in servcert.pem) +*/ +int +_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl) +{ + X509 *peer_cert; + EVP_PKEY *peer_pkey; + /* EVP_PKEY *ca_pkey; */ + /* EVP_PKEY *tmp_pkey; */ + char notBefore[64]; + char notAfter[64]; + int alg; + int sign_alg; + + + if (!(peer_cert = SSL_get_peer_certificate (ssl))) + return (1); /* FATAL? */ + + X509_NAME_oneline (X509_get_subject_name (peer_cert), cert_info->subject, + sizeof (cert_info->subject)); + X509_NAME_oneline (X509_get_issuer_name (peer_cert), cert_info->issuer, + sizeof (cert_info->issuer)); + broke_oneline (cert_info->subject, cert_info->subject_word); + broke_oneline (cert_info->issuer, cert_info->issuer_word); + + alg = OBJ_obj2nid (peer_cert->cert_info->key->algor->algorithm); + sign_alg = OBJ_obj2nid (peer_cert->sig_alg->algorithm); + ASN1_TIME_snprintf (notBefore, sizeof (notBefore), + X509_get_notBefore (peer_cert)); + ASN1_TIME_snprintf (notAfter, sizeof (notAfter), + X509_get_notAfter (peer_cert)); + + peer_pkey = X509_get_pubkey (peer_cert); + + strncpy (cert_info->algorithm, + (alg == NID_undef) ? "Unknown" : OBJ_nid2ln (alg), + sizeof (cert_info->algorithm)); + cert_info->algorithm_bits = EVP_PKEY_bits (peer_pkey); + strncpy (cert_info->sign_algorithm, + (sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln (sign_alg), + sizeof (cert_info->sign_algorithm)); + /* EVP_PKEY_bits(ca_pkey)); */ + cert_info->sign_algorithm_bits = 0; + strncpy (cert_info->notbefore, notBefore, sizeof (cert_info->notbefore)); + strncpy (cert_info->notafter, notAfter, sizeof (cert_info->notafter)); + + EVP_PKEY_free (peer_pkey); + + /* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */ +/* + if (ssl->session->sess_cert->peer_rsa_tmp) { + tmp_pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(tmp_pkey, ssl->session->sess_cert->peer_rsa_tmp); + cert_info->rsa_tmp_bits = EVP_PKEY_bits (tmp_pkey); + EVP_PKEY_free(tmp_pkey); + } else + fprintf(stderr, "REMOTE SIDE DOESN'T PROVIDES ->peer_rsa_tmp\n"); +*/ + cert_info->rsa_tmp_bits = 0; + + X509_free (peer_cert); + + return (0); +} + + +struct chiper_info * +_SSL_get_cipher_info (SSL * ssl) +{ + SSL_CIPHER *c; + + + c = SSL_get_current_cipher (ssl); + strncpy (chiper_info.version, SSL_CIPHER_get_version (c), + sizeof (chiper_info.version)); + strncpy (chiper_info.chiper, SSL_CIPHER_get_name (c), + sizeof (chiper_info.chiper)); + SSL_CIPHER_get_bits (c, &chiper_info.chiper_bits); + + return (&chiper_info); +} + + +int +_SSL_send (SSL * ssl, char *buf, int len) +{ + int num; + + + num = SSL_write (ssl, buf, len); + + switch (SSL_get_error (ssl, num)) + { + case SSL_ERROR_SSL: /* setup errno! */ + /* ??? */ + __SSL_fill_err_buf ("SSL_write"); + fprintf (stderr, "%s\n", err_buf); + break; + case SSL_ERROR_SYSCALL: + /* ??? */ + perror ("SSL_write/write"); + break; + case SSL_ERROR_ZERO_RETURN: + /* fprintf(stderr, "SSL closed on write\n"); */ + break; + } + + return (num); +} + + +int +_SSL_recv (SSL * ssl, char *buf, int len) +{ + int num; + + + num = SSL_read (ssl, buf, len); + + switch (SSL_get_error (ssl, num)) + { + case SSL_ERROR_SSL: + /* ??? */ + __SSL_fill_err_buf ("SSL_read"); + fprintf (stderr, "%s\n", err_buf); + break; + case SSL_ERROR_SYSCALL: + /* ??? */ + if (!would_block ()) + perror ("SSL_read/read"); + break; + case SSL_ERROR_ZERO_RETURN: + /* fprintf(stdeerr, "SSL closed on read\n"); */ + break; + } + + return (num); +} + + +SSL * +_SSL_socket (SSL_CTX *ctx, int sd) +{ + SSL *ssl; + + + if (!(ssl = SSL_new (ctx))) + /* FATAL */ + __SSL_critical_error ("SSL_new"); + + SSL_set_fd (ssl, sd); + if (ctx->method == SSLv3_client_method()) + SSL_set_connect_state (ssl); + else + SSL_set_accept_state(ssl); + + return (ssl); +} + + +char * +_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert) +{ + if (!SSL_CTX_set_default_verify_paths (ctx)) + { + __SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths"); + return (err_buf); + } +/* + if (cacert) + { + if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL)) + { + __SSL_fill_err_buf ("SSL_CTX_load_verify_locations"); + return (err_buf); + } + } +*/ + SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback); + + return (NULL); +} + + +void +_SSL_close (SSL * ssl) +{ + SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); + SSL_free (ssl); + ERR_remove_state (0); /* free state buffer */ +} diff --git a/src/common/ssl.h b/src/common/ssl.h new file mode 100644 index 00000000..26eb0f88 --- /dev/null +++ b/src/common/ssl.h @@ -0,0 +1,65 @@ +/* + ... +*/ + +struct cert_info { + char subject[256]; + char *subject_word[12]; + char issuer[256]; + char *issuer_word[12]; + char algorithm[32]; + int algorithm_bits; + char sign_algorithm[32]; + int sign_algorithm_bits; + char notbefore[32]; + char notafter[32]; + + int rsa_tmp_bits; +}; + +struct chiper_info { + char version[16]; + char chiper[24]; + int chiper_bits; +}; + +SSL_CTX *_SSL_context_init (void (*info_cb_func), int server); +#define _SSL_context_free(a) SSL_CTX_free(a); + +SSL *_SSL_socket (SSL_CTX *ctx, int sd); +char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert); +/* + int SSL_connect(SSL *); + int SSL_accept(SSL *); + int SSL_get_fd(SSL *); +*/ +void _SSL_close (SSL * ssl); + +int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl); +struct chiper_info *_SSL_get_cipher_info (SSL * ssl); + +/*char *_SSL_add_keypair (SSL_CTX *ctx, char *privkey, char *cert);*/ +/*void _SSL_add_random_keypair(SSL_CTX *ctx, int bits);*/ + +int _SSL_send (SSL * ssl, char *buf, int len); +int _SSL_recv (SSL * ssl, char *buf, int len); + +/* misc */ +/*void broke_oneline (char *oneline, char *parray[]);*/ + +/*char *_SSL_do_cipher_base64(char *buf, int buf_len, char *key, int operation);*/ /* must be freed */ + +/*void *_SSL_get_sess_obj(SSL *ssl, int type);*/ /* NOT must be freed */ +#define _SSL_get_sess_pkey(a) _SSL_get_sess_obj(a, 0) +#define _SSL_get_sess_prkey(a) _SSL_get_sess_obj(a, 1) +#define _SSL_get_sess_x509(a) _SSL_get_sess_obj(a, 2) +/*char *_SSL_get_obj_base64(void *s, int type);*/ /* must be freed */ +#define _SSL_get_pkey_base64(a) _SSL_get_obj_base64(a, 0) +#define _SSL_get_prkey_base64(a) _SSL_get_obj_base64(a, 1) +#define _SSL_get_x509_base64(a) _SSL_get_obj_base64(a, 2) +/*char *_SSL_get_ctx_obj_base64(SSL_CTX *ctx, int type);*/ /* must be freed */ +#define _SSL_get_ctx_pkey_base64(a) _SSL_get_ctx_obj_base64(a, 0) +#define _SSL_get_ctx_prkey_base64(a) _SSL_get_ctx_obj_base64(a, 1) +#define _SSL_get_ctx_x509_base64(a) _SSL_get_ctx_obj_base64(a, 2) + +/*int _SSL_verify_x509(X509 *x509);*/ diff --git a/src/common/text.c b/src/common/text.c new file mode 100644 index 00000000..a2198517 --- /dev/null +++ b/src/common/text.c @@ -0,0 +1,2309 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include "xchat.h" +#include <glib/ghash.h> +#include "cfgfiles.h" +#include "chanopt.h" +#include "plugin.h" +#include "fe.h" +#include "server.h" +#include "util.h" +#include "outbound.h" +#include "xchatc.h" +#include "text.h" +#ifdef WIN32 +#include <windows.h> +#endif + +struct pevt_stage1 +{ + int len; + char *data; + struct pevt_stage1 *next; +}; + + +static void mkdir_p (char *dir); +static char *log_create_filename (char *channame); + + +static char * +scrollback_get_filename (session *sess, char *buf, int max) +{ + char *net, *chan; + + net = server_get_network (sess->server, FALSE); + if (!net) + return NULL; + + snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, ""); + mkdir_p (buf); + + chan = log_create_filename (sess->channel); + snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, chan); + free (chan); + + return buf; +} + +#if 0 + +static void +scrollback_unlock (session *sess) +{ + char buf[1024]; + + if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL) + return; + + strcat (buf, ".lock"); + unlink (buf); +} + +static gboolean +scrollback_lock (session *sess) +{ + char buf[1024]; + int fh; + + if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL) + return FALSE; + + strcat (buf, ".lock"); + + if (access (buf, F_OK) == 0) + return FALSE; /* can't get lock */ + + fh = open (buf, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644); + if (fh == -1) + return FALSE; + + return TRUE; +} + +#endif + +void +scrollback_close (session *sess) +{ + if (sess->scrollfd != -1) + { + close (sess->scrollfd); + sess->scrollfd = -1; + } +} + +static char * +file_to_buffer (char *file, int *len) +{ + int fh; + char *buf; + struct stat st; + + fh = open (file, O_RDONLY | OFLAGS); + if (fh == -1) + return NULL; + + fstat (fh, &st); + + buf = malloc (st.st_size); + if (!buf) + { + close (fh); + return NULL; + } + + if (read (fh, buf, st.st_size) != st.st_size) + { + free (buf); + close (fh); + return NULL; + } + + *len = st.st_size; + close (fh); + return buf; +} + +/* shrink the file to roughly prefs.max_lines */ + +static void +scrollback_shrink (session *sess) +{ + char file[1024]; + char *buf; + int fh; + int lines; + int line; + int len; + char *p; + + scrollback_close (sess); + sess->scrollwritten = 0; + lines = 0; + + if (scrollback_get_filename (sess, file, sizeof (file)) == NULL) + return; + + buf = file_to_buffer (file, &len); + if (!buf) + return; + + /* count all lines */ + p = buf; + while (p != buf + len) + { + if (*p == '\n') + lines++; + p++; + } + + fh = open (file, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644); + if (fh == -1) + { + free (buf); + return; + } + + line = 0; + p = buf; + while (p != buf + len) + { + if (*p == '\n') + { + line++; + if (line >= lines - prefs.max_lines && + p + 1 != buf + len) + { + p++; + write (fh, p, len - (p - buf)); + break; + } + } + p++; + } + + close (fh); + free (buf); +} + +static void +scrollback_save (session *sess, char *text) +{ + char buf[512 * 4]; + time_t stamp; + int len; + + if (sess->type == SESS_SERVER) + return; + + if (sess->text_scrollback == SET_DEFAULT) + { + if (!prefs.text_replay) + return; + } + else + { + if (sess->text_scrollback != SET_ON) + return; + } + + if (sess->scrollfd == -1) + { + if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL) + return; + + sess->scrollfd = open (buf, O_CREAT | O_APPEND | O_WRONLY, 0644); + if (sess->scrollfd == -1) + return; + } + + stamp = time (0); + if (sizeof (stamp) == 4) /* gcc will optimize one of these out */ + write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %d ", (int)stamp)); + else + write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %"G_GINT64_FORMAT" ", (gint64)stamp)); + + len = strlen (text); + write (sess->scrollfd, text, len); + if (len && text[len - 1] != '\n') + write (sess->scrollfd, "\n", 1); + + sess->scrollwritten++; + + if ((sess->scrollwritten * 2 > prefs.max_lines && prefs.max_lines > 0) || + sess->scrollwritten > 32000) + scrollback_shrink (sess); +} + +void +scrollback_load (session *sess) +{ + int fh; + char buf[512 * 4]; + char *text; + time_t stamp; + int lines; + char *map, *end_map; + struct stat statbuf; + const char *begin, *eol; + + if (sess->text_scrollback == SET_DEFAULT) + { + if (!prefs.text_replay) + return; + } + else + { + if (sess->text_scrollback != SET_ON) + return; + } + + if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL) + return; + + fh = open (buf, O_RDONLY | OFLAGS); + if (fh == -1) + return; + + if (fstat (fh, &statbuf) < 0) + return; + + map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fh, 0); + if (map == MAP_FAILED) + return; + + end_map = map + statbuf.st_size; + + lines = 0; + begin = map; + while (begin < end_map) + { + int n_bytes; + + eol = memchr (begin, '\n', end_map - begin); + + if (!eol) + eol = end_map; + + n_bytes = MIN (eol - begin, sizeof (buf) - 1); + + strncpy (buf, begin, n_bytes); + + buf[n_bytes] = 0; + + if (buf[0] == 'T') + { + if (sizeof (time_t) == 4) + stamp = strtoul (buf + 2, NULL, 10); + else + stamp = strtoull (buf + 2, NULL, 10); /* just incase time_t is 64 bits */ + text = strchr (buf + 3, ' '); + if (text) + { + text = strip_color (text + 1, -1, STRIP_COLOR); + fe_print_text (sess, text, stamp); + g_free (text); + } + lines++; + } + + begin = eol + 1; + } + + sess->scrollwritten = lines; + + if (lines) + { + text = ctime (&stamp); + text[24] = 0; /* get rid of the \n */ + snprintf (buf, sizeof (buf), "\n*\t%s %s\n\n", _("Loaded log from"), text); + fe_print_text (sess, buf, 0); + /*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/ + } + + munmap (map, statbuf.st_size); + close (fh); +} + +void +log_close (session *sess) +{ + char obuf[512]; + time_t currenttime; + + if (sess->logfd != -1) + { + currenttime = time (NULL); + write (sess->logfd, obuf, + snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"), + ctime (¤ttime))); + close (sess->logfd); + sess->logfd = -1; + } +} + +static void +mkdir_p (char *dir) /* like "mkdir -p" from a shell, FS encoding */ +{ + char *start = dir; + + /* the whole thing already exists? */ + if (access (dir, F_OK) == 0) + return; + + while (*dir) + { +#ifdef WIN32 + if (dir != start && (*dir == '/' || *dir == '\\')) +#else + if (dir != start && *dir == '/') +#endif + { + *dir = 0; +#ifdef WIN32 + mkdir (start); +#else + mkdir (start, S_IRUSR | S_IWUSR | S_IXUSR); +#endif + *dir = '/'; + } + dir++; + } +} + +static char * +log_create_filename (char *channame) +{ + char *tmp, *ret; + int mbl; + + ret = tmp = strdup (channame); + while (*tmp) + { + mbl = g_utf8_skip[((unsigned char *)tmp)[0]]; + if (mbl == 1) + { +#ifndef WIN32 + *tmp = rfc_tolower (*tmp); + if (*tmp == '/') +#else + /* win32 can't handle filenames with \|/><:"*? characters */ + if (*tmp == '\\' || *tmp == '|' || *tmp == '/' || + *tmp == '>' || *tmp == '<' || *tmp == ':' || + *tmp == '\"' || *tmp == '*' || *tmp == '?') +#endif + *tmp = '_'; + } + tmp += mbl; + } + + return ret; +} + +/* like strcpy, but % turns into %% */ + +static char * +log_escape_strcpy (char *dest, char *src, char *end) +{ + while (*src) + { + *dest = *src; + if (dest + 1 == end) + break; + dest++; + src++; + + if (*src == '%') + { + if (dest + 1 == end) + break; + dest[0] = '%'; + dest++; + } + } + + dest[0] = 0; + return dest - 1; +} + +/* substitutes %c %n %s into buffer */ + +static void +log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s) +{ + char *end = buf + bufsize; + + while (1) + { + switch (fmt[0]) + { + case 0: + buf[0] = 0; + return; + + case '%': + fmt++; + switch (fmt[0]) + { + case 'c': + buf = log_escape_strcpy (buf, c, end); + break; + case 'n': + buf = log_escape_strcpy (buf, n, end); + break; + case 's': + buf = log_escape_strcpy (buf, s, end); + break; + default: + buf[0] = '%'; + buf++; + buf[0] = fmt[0]; + break; + } + break; + + default: + buf[0] = fmt[0]; + } + fmt++; + buf++; + /* doesn't fit? */ + if (buf == end) + { + buf[-1] = 0; + return; + } + } +} + +static char * +log_create_pathname (char *servname, char *channame, char *netname) +{ + char fname[384]; + char fnametime[384]; + char *fs; + struct tm *tm; + time_t now; + + if (!netname) + netname = "NETWORK"; + + /* first, everything is in UTF-8 */ + if (!rfc_casecmp (channame, servname)) + channame = strdup ("server"); + else + channame = log_create_filename (channame); + log_insert_vars (fname, sizeof (fname), prefs.logmask, channame, netname, servname); + free (channame); + + /* insert time/date */ + now = time (NULL); + tm = localtime (&now); + strftime (fnametime, sizeof (fnametime), fname, tm); + + /* create final path/filename */ +#ifdef WIN32 + if (fnametime[0] == '/' || (fnametime[0] >= 'A' && fnametime[1] == ':')) +#else + if (fnametime[0] == '/') /* is it fullpath already? */ +#endif + snprintf (fname, sizeof (fname), "%s", fnametime); + else + snprintf (fname, sizeof (fname), "%s/xchatlogs/%s", get_xdir_utf8 (), fnametime); + + /* now we need it in FileSystem encoding */ + fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0); + + /* create all the subdirectories */ + if (fs) + mkdir_p (fs); + + return fs; +} + +static int +log_open_file (char *servname, char *channame, char *netname) +{ + char buf[512]; + int fd; + char *file; + time_t currenttime; + + file = log_create_pathname (servname, channame, netname); + if (!file) + return -1; + +#ifdef WIN32 + fd = open (file, O_CREAT | O_APPEND | O_WRONLY, S_IREAD|S_IWRITE); +#else + fd = open (file, O_CREAT | O_APPEND | O_WRONLY, 0644); +#endif + g_free (file); + + if (fd == -1) + return -1; + currenttime = time (NULL); + write (fd, buf, + snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"), + ctime (¤ttime))); + + return fd; +} + +static void +log_open (session *sess) +{ + static gboolean log_error = FALSE; + + log_close (sess); + sess->logfd = log_open_file (sess->server->servername, sess->channel, + server_get_network (sess->server, FALSE)); + + if (!log_error && sess->logfd == -1) + { + char message[512]; + snprintf (message, sizeof (message), + _("* Can't open log file(s) for writing. Check the\n" \ + " permissions on %s/xchatlogs"), get_xdir_utf8 ()); + fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR); + + log_error = TRUE; + } +} + +void +log_open_or_close (session *sess) +{ + if (sess->text_logging == SET_DEFAULT) + { + if (prefs.logging) + log_open (sess); + else + log_close (sess); + } + else + { + if (sess->text_logging) + log_open (sess); + else + log_close (sess); + } +} + +int +get_stamp_str (char *fmt, time_t tim, char **ret) +{ + char *loc = NULL; + char dest[128]; + gsize len; + + /* strftime wants the format string in LOCALE! */ + if (!prefs.utf8_locale) + { + const gchar *charset; + + g_get_charset (&charset); + loc = g_convert_with_fallback (fmt, -1, charset, "UTF-8", "?", 0, 0, 0); + if (loc) + fmt = loc; + } + + len = strftime (dest, sizeof (dest), fmt, localtime (&tim)); + if (len) + { + if (prefs.utf8_locale) + *ret = g_strdup (dest); + else + *ret = g_locale_to_utf8 (dest, len, 0, &len, 0); + } + + if (loc) + g_free (loc); + + return len; +} + +static void +log_write (session *sess, char *text) +{ + char *temp; + char *stamp; + char *file; + int len; + + if (sess->text_logging == SET_DEFAULT) + { + if (!prefs.logging) + return; + } + else + { + if (sess->text_logging != SET_ON) + return; + } + + if (sess->logfd == -1) + log_open (sess); + + /* change to a different log file? */ + file = log_create_pathname (sess->server->servername, sess->channel, + server_get_network (sess->server, FALSE)); + if (file) + { + if (access (file, F_OK) != 0) + { + close (sess->logfd); + sess->logfd = log_open_file (sess->server->servername, sess->channel, + server_get_network (sess->server, FALSE)); + } + g_free (file); + } + + if (prefs.timestamp_logs) + { + len = get_stamp_str (prefs.timestamp_log_format, time (0), &stamp); + if (len) + { + write (sess->logfd, stamp, len); + g_free (stamp); + } + } + temp = strip_color (text, -1, STRIP_ALL); + len = strlen (temp); + write (sess->logfd, temp, len); + /* lots of scripts/plugins print without a \n at the end */ + if (temp[len - 1] != '\n') + write (sess->logfd, "\n", 1); /* emulate what xtext would display */ + g_free (temp); +} + +/* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8 */ +/* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */ +/* 2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */ +/* thing useful in this range and it helps us receive from mIRC */ +/* 3. The five undefined chars in CP1252 80-9f are replaced with */ +/* ISO-8859-15 control codes. */ +/* 4. Handles 0xa4 as a Euro symbol ala ISO-8859-15. */ +/* 5. Uses ISO-8859-1 (which matches CP1252) for everything else. */ +/* 6. This routine measured 3x faster than g_convert :) */ + +static unsigned char * +iso_8859_1_to_utf8 (unsigned char *text, int len, gsize *bytes_written) +{ + unsigned int idx; + unsigned char *res, *output; + static const unsigned short lowtable[] = /* 74 byte table for 80-a4 */ + { + /* compressed utf-8 table: if the first byte's 0x20 bit is set, it + indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */ + 0x82ac, /* 80 Euro. CP1252 from here on... */ + 0xe281, /* 81 NA */ + 0x809a, /* 82 */ + 0xe692, /* 83 */ + 0x809e, /* 84 */ + 0x80a6, /* 85 */ + 0x80a0, /* 86 */ + 0x80a1, /* 87 */ + 0xeb86, /* 88 */ + 0x80b0, /* 89 */ + 0xe5a0, /* 8a */ + 0x80b9, /* 8b */ + 0xe592, /* 8c */ + 0xe28d, /* 8d NA */ + 0xe5bd, /* 8e */ + 0xe28f, /* 8f NA */ + 0xe290, /* 90 NA */ + 0x8098, /* 91 */ + 0x8099, /* 92 */ + 0x809c, /* 93 */ + 0x809d, /* 94 */ + 0x80a2, /* 95 */ + 0x8093, /* 96 */ + 0x8094, /* 97 */ + 0xeb9c, /* 98 */ + 0x84a2, /* 99 */ + 0xe5a1, /* 9a */ + 0x80ba, /* 9b */ + 0xe593, /* 9c */ + 0xe29d, /* 9d NA */ + 0xe5be, /* 9e */ + 0xe5b8, /* 9f */ + 0xe2a0, /* a0 */ + 0xe2a1, /* a1 */ + 0xe2a2, /* a2 */ + 0xe2a3, /* a3 */ + 0x82ac /* a4 ISO-8859-15 Euro. */ + }; + + if (len == -1) + len = strlen (text); + + /* worst case scenario: every byte turns into 3 bytes */ + res = output = g_malloc ((len * 3) + 1); + if (!output) + return NULL; + + while (len) + { + if (G_LIKELY (*text < 0x80)) + { + *output = *text; /* ascii maps directly */ + } + else if (*text <= 0xa4) /* 80-a4 use a lookup table */ + { + idx = *text - 0x80; + if (lowtable[idx] & 0x2000) + { + *output++ = (lowtable[idx] >> 8) & 0xdf; /* 2 byte utf-8 */ + *output = lowtable[idx] & 0xff; + } + else + { + *output++ = 0xe2; /* 3 byte utf-8 */ + *output++ = (lowtable[idx] >> 8) & 0xff; + *output = lowtable[idx] & 0xff; + } + } + else if (*text < 0xc0) + { + *output++ = 0xc2; + *output = *text; + } + else + { + *output++ = 0xc3; + *output = *text - 0x40; + } + output++; + text++; + len--; + } + *output = 0; /* terminate */ + *bytes_written = output - res; + + return res; +} + +char * +text_validate (char **text, int *len) +{ + char *utf; + gsize utf_len; + + /* valid utf8? */ + if (g_utf8_validate (*text, *len, 0)) + return NULL; + +#ifdef WIN32 + if (GetACP () == 1252) /* our routine is better than iconv's 1252 */ +#else + if (prefs.utf8_locale) +#endif + /* fallback to iso-8859-1 */ + utf = iso_8859_1_to_utf8 (*text, *len, &utf_len); + else + { + /* fallback to locale */ + utf = g_locale_to_utf8 (*text, *len, 0, &utf_len, NULL); + if (!utf) + utf = iso_8859_1_to_utf8 (*text, *len, &utf_len); + } + + if (!utf) + { + *text = g_strdup ("%INVALID%"); + *len = 9; + } else + { + *text = utf; + *len = utf_len; + } + + return utf; +} + +void +PrintText (session *sess, char *text) +{ + char *conv; + + if (!sess) + { + if (!sess_list) + return; + sess = (session *) sess_list->data; + } + + /* make sure it's valid utf8 */ + if (text[0] == 0) + { + text = "\n"; + conv = NULL; + } else + { + int len = -1; + conv = text_validate ((char **)&text, &len); + } + + log_write (sess, text); + scrollback_save (sess, text); + fe_print_text (sess, text, 0); + + if (conv) + g_free (conv); +} + +void +PrintTextf (session *sess, char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + PrintText (sess, buf); + g_free (buf); +} + +/* Print Events stuff here --AGL */ + +/* Consider the following a NOTES file: + + The main upshot of this is: + * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like + * The default text engine can be config'ed + + By default it should appear *exactly* the same (I'm working hard not to change the default style) but if you go into Settings->Edit Event Texts you can change the text's. The format is thus: + + The normal %Cx (color) and %B (bold) etc work + + $x is replaced with the data in var x (e.g. $1 is often the nick) + + $axxx is replace with a single byte of value xxx (in base 10) + + AGL (990507) + */ + +/* These lists are thus: + pntevts_text[] are the strings the user sees (WITH %x etc) + pntevts[] are the data strings with \000 etc + */ + +/* To add a new event: + + Think up a name (like "Join") + Make up a pevt_name_help struct + Add an entry to textevents.in + Type: make textevents + */ + +/* Internals: + + On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the + defaults are loaded. Any missing events are filled from defaults. + Each event is parsed by pevt_build_string and a binary output is produced + which looks like: + + (byte) value: 0 = { + (int) numbers of bytes + (char []) that number of byte to be memcpy'ed into the buffer + } + 1 = + (byte) number of varable to insert + 2 = end of buffer + + Each XP_TE_* signal is hard coded to call text_emit which calls + display_event which decodes the data + + This means that this system *should be faster* than snprintf because + it always 'knows' that format of the string (basically is preparses much + of the work) + + --AGL + */ + +char *pntevts_text[NUM_XP]; +char *pntevts[NUM_XP]; + +#define pevt_generic_none_help NULL + +static char * const pevt_genmsg_help[] = { + N_("Left message"), + N_("Right message"), +}; + +static char * const pevt_join_help[] = { + N_("The nick of the joining person"), + N_("The channel being joined"), + N_("The host of the person"), +}; + +static char * const pevt_chanaction_help[] = { + N_("Nickname"), + N_("The action"), + N_("Mode char"), + N_("Identified text"), +}; + +static char * const pevt_chanmsg_help[] = { + N_("Nickname"), + N_("The text"), + N_("Mode char"), + N_("Identified text"), +}; + +static char * const pevt_privmsg_help[] = { + N_("Nickname"), + N_("The message"), + N_("Identified text") +}; + +static char * const pevt_changenick_help[] = { + N_("Old nickname"), + N_("New nickname"), +}; + +static char * const pevt_newtopic_help[] = { + N_("Nick of person who changed the topic"), + N_("Topic"), + N_("Channel"), +}; + +static char * const pevt_topic_help[] = { + N_("Channel"), + N_("Topic"), +}; + +static char * const pevt_kick_help[] = { + N_("The nickname of the kicker"), + N_("The person being kicked"), + N_("The channel"), + N_("The reason"), +}; + +static char * const pevt_part_help[] = { + N_("The nick of the person leaving"), + N_("The host of the person"), + N_("The channel"), +}; + +static char * const pevt_chandate_help[] = { + N_("The channel"), + N_("The time"), +}; + +static char * const pevt_topicdate_help[] = { + N_("The channel"), + N_("The creator"), + N_("The time"), +}; + +static char * const pevt_quit_help[] = { + N_("Nick"), + N_("Reason"), + N_("Host"), +}; + +static char * const pevt_pingrep_help[] = { + N_("Who it's from"), + N_("The time in x.x format (see below)"), +}; + +static char * const pevt_notice_help[] = { + N_("Who it's from"), + N_("The message"), +}; + +static char * const pevt_channotice_help[] = { + N_("Who it's from"), + N_("The Channel it's going to"), + N_("The message"), +}; + +static char * const pevt_uchangenick_help[] = { + N_("Old nickname"), + N_("New nickname"), +}; + +static char * const pevt_ukick_help[] = { + N_("The person being kicked"), + N_("The channel"), + N_("The nickname of the kicker"), + N_("The reason"), +}; + +static char * const pevt_partreason_help[] = { + N_("The nick of the person leaving"), + N_("The host of the person"), + N_("The channel"), + N_("The reason"), +}; + +static char * const pevt_ctcpsnd_help[] = { + N_("The sound"), + N_("The nick of the person"), + N_("The channel"), +}; + +static char * const pevt_ctcpgen_help[] = { + N_("The CTCP event"), + N_("The nick of the person"), +}; + +static char * const pevt_ctcpgenc_help[] = { + N_("The CTCP event"), + N_("The nick of the person"), + N_("The Channel it's going to"), +}; + +static char * const pevt_chansetkey_help[] = { + N_("The nick of the person who set the key"), + N_("The key"), +}; + +static char * const pevt_chansetlimit_help[] = { + N_("The nick of the person who set the limit"), + N_("The limit"), +}; + +static char * const pevt_chanop_help[] = { + N_("The nick of the person who did the op'ing"), + N_("The nick of the person who has been op'ed"), +}; + +static char * const pevt_chanhop_help[] = { + N_("The nick of the person who has been halfop'ed"), + N_("The nick of the person who did the halfop'ing"), +}; + +static char * const pevt_chanvoice_help[] = { + N_("The nick of the person who did the voice'ing"), + N_("The nick of the person who has been voice'ed"), +}; + +static char * const pevt_chanban_help[] = { + N_("The nick of the person who did the banning"), + N_("The ban mask"), +}; + +static char * const pevt_chanrmkey_help[] = { + N_("The nick who removed the key"), +}; + +static char * const pevt_chanrmlimit_help[] = { + N_("The nick who removed the limit"), +}; + +static char * const pevt_chandeop_help[] = { + N_("The nick of the person of did the deop'ing"), + N_("The nick of the person who has been deop'ed"), +}; +static char * const pevt_chandehop_help[] = { + N_("The nick of the person of did the dehalfop'ing"), + N_("The nick of the person who has been dehalfop'ed"), +}; + +static char * const pevt_chandevoice_help[] = { + N_("The nick of the person of did the devoice'ing"), + N_("The nick of the person who has been devoice'ed"), +}; + +static char * const pevt_chanunban_help[] = { + N_("The nick of the person of did the unban'ing"), + N_("The ban mask"), +}; + +static char * const pevt_chanexempt_help[] = { + N_("The nick of the person who did the exempt"), + N_("The exempt mask"), +}; + +static char * const pevt_chanrmexempt_help[] = { + N_("The nick of the person removed the exempt"), + N_("The exempt mask"), +}; + +static char * const pevt_chaninvite_help[] = { + N_("The nick of the person who did the invite"), + N_("The invite mask"), +}; + +static char * const pevt_chanrminvite_help[] = { + N_("The nick of the person removed the invite"), + N_("The invite mask"), +}; + +static char * const pevt_chanmodegen_help[] = { + N_("The nick of the person setting the mode"), + N_("The mode's sign (+/-)"), + N_("The mode letter"), + N_("The channel it's being set on"), +}; + +static char * const pevt_whois1_help[] = { + N_("Nickname"), + N_("Username"), + N_("Host"), + N_("Full name"), +}; + +static char * const pevt_whois2_help[] = { + N_("Nickname"), + N_("Channel Membership/\"is an IRC operator\""), +}; + +static char * const pevt_whois3_help[] = { + N_("Nickname"), + N_("Server Information"), +}; + +static char * const pevt_whois4_help[] = { + N_("Nickname"), + N_("Idle time"), +}; + +static char * const pevt_whois4t_help[] = { + N_("Nickname"), + N_("Idle time"), + N_("Signon time"), +}; + +static char * const pevt_whois5_help[] = { + N_("Nickname"), + N_("Away reason"), +}; + +static char * const pevt_whois6_help[] = { + N_("Nickname"), +}; + +static char * const pevt_whoisid_help[] = { + N_("Nickname"), + N_("Message"), + "Numeric" +}; + +static char * const pevt_whoisauth_help[] = { + N_("Nickname"), + N_("Message"), + N_("Account"), +}; + +static char * const pevt_whoisrealhost_help[] = { + N_("Nickname"), + N_("Real user@host"), + N_("Real IP"), + N_("Message"), +}; + +static char * const pevt_generic_channel_help[] = { + N_("Channel Name"), +}; + +static char * const pevt_servertext_help[] = { + N_("Text"), + N_("Server Name"), + N_("Raw Numeric or Identifier") +}; + +static char * const pevt_sslmessage_help[] = { + N_("Text"), + N_("Server Name") +}; + +static char * const pevt_invited_help[] = { + N_("Channel Name"), + N_("Nick of person who invited you"), + N_("Server Name"), +}; + +static char * const pevt_usersonchan_help[] = { + N_("Channel Name"), + N_("Users"), +}; + +static char * const pevt_nickclash_help[] = { + N_("Nickname in use"), + N_("Nick being tried"), +}; + +static char * const pevt_connfail_help[] = { + N_("Error"), +}; + +static char * const pevt_connect_help[] = { + N_("Host"), + N_("IP"), + N_("Port"), +}; + +static char * const pevt_sconnect_help[] = { + "PID" +}; + +static char * const pevt_generic_nick_help[] = { + N_("Nickname"), + N_("Server Name"), + N_("Network") +}; + +static char * const pevt_chanmodes_help[] = { + N_("Channel Name"), + N_("Modes string"), +}; + +static char * const pevt_rawmodes_help[] = { + N_("Nickname"), + N_("Modes string"), +}; + +static char * const pevt_kill_help[] = { + N_("Nickname"), + N_("Reason"), +}; + +static char * const pevt_dccchaterr_help[] = { + N_("Nickname"), + N_("IP address"), + N_("Port"), + N_("Error"), +}; + +static char * const pevt_dccstall_help[] = { + N_("DCC Type"), + N_("Filename"), + N_("Nickname"), +}; + +static char * const pevt_generic_file_help[] = { + N_("Filename"), + N_("Error"), +}; + +static char * const pevt_dccrecverr_help[] = { + N_("Filename"), + N_("Destination filename"), + N_("Nickname"), + N_("Error"), +}; + +static char * const pevt_dccrecvcomp_help[] = { + N_("Filename"), + N_("Destination filename"), + N_("Nickname"), + N_("CPS"), +}; + +static char * const pevt_dccconfail_help[] = { + N_("DCC Type"), + N_("Nickname"), + N_("Error"), +}; + +static char * const pevt_dccchatcon_help[] = { + N_("Nickname"), + N_("IP address"), +}; + +static char * const pevt_dcccon_help[] = { + N_("Nickname"), + N_("IP address"), + N_("Filename"), +}; + +static char * const pevt_dccsendfail_help[] = { + N_("Filename"), + N_("Nickname"), + N_("Error"), +}; + +static char * const pevt_dccsendcomp_help[] = { + N_("Filename"), + N_("Nickname"), + N_("CPS"), +}; + +static char * const pevt_dccoffer_help[] = { + N_("Filename"), + N_("Nickname"), + N_("Pathname"), +}; + +static char * const pevt_dccfileabort_help[] = { + N_("Nickname"), + N_("Filename") +}; + +static char * const pevt_dccchatabort_help[] = { + N_("Nickname"), +}; + +static char * const pevt_dccresumeoffer_help[] = { + N_("Nickname"), + N_("Filename"), + N_("Position"), +}; + +static char * const pevt_dccsendoffer_help[] = { + N_("Nickname"), + N_("Filename"), + N_("Size"), + N_("IP address"), +}; + +static char * const pevt_dccgenericoffer_help[] = { + N_("DCC String"), + N_("Nickname"), +}; + +static char * const pevt_notifynumber_help[] = { + N_("Number of notify items"), +}; + +static char * const pevt_serverlookup_help[] = { + N_("Server Name"), +}; + +static char * const pevt_servererror_help[] = { + N_("Text"), +}; + +static char * const pevt_foundip_help[] = { + N_("IP"), +}; + +static char * const pevt_dccrename_help[] = { + N_("Old Filename"), + N_("New Filename"), +}; + +static char * const pevt_ctcpsend_help[] = { + N_("Receiver"), + N_("Message"), +}; + +static char * const pevt_ignoreaddremove_help[] = { + N_("Hostmask"), +}; + +static char * const pevt_resolvinguser_help[] = { + N_("Nickname"), + N_("Hostname"), +}; + +static char * const pevt_malformed_help[] = { + N_("Nickname"), + N_("The Packet"), +}; + +static char * const pevt_pingtimeout_help[] = { + N_("Seconds"), +}; + +static char * const pevt_uinvite_help[] = { + N_("Nick of person who have been invited"), + N_("Channel Name"), + N_("Server Name"), +}; + +static char * const pevt_banlist_help[] = { + N_("Channel"), + N_("Banmask"), + N_("Who set the ban"), + N_("Ban time"), +}; + +static char * const pevt_discon_help[] = { + N_("Error"), +}; + +#include "textevents.h" + +static void +pevent_load_defaults () +{ + int i; + + for (i = 0; i < NUM_XP; i++) + { + if (pntevts_text[i]) + free (pntevts_text[i]); + + /* make-te.c sets this 128 flag (DON'T call gettext() flag) */ + if (te[i].num_args & 128) + pntevts_text[i] = strdup (te[i].def); + else + pntevts_text[i] = strdup (_(te[i].def)); + } +} + +void +pevent_make_pntevts () +{ + int i, m; + char out[1024]; + + for (i = 0; i < NUM_XP; i++) + { + if (pntevts[i] != NULL) + free (pntevts[i]); + if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0) + { + snprintf (out, sizeof (out), + _("Error parsing event %s.\nLoading default."), te[i].name); + fe_message (out, FE_MSG_WARN); + free (pntevts_text[i]); + /* make-te.c sets this 128 flag (DON'T call gettext() flag) */ + if (te[i].num_args & 128) + pntevts_text[i] = strdup (te[i].def); + else + pntevts_text[i] = strdup (_(te[i].def)); + if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0) + { + fprintf (stderr, + "XChat CRITICAL *** default event text failed to build!\n"); + abort (); + } + } + } +} + +/* Loading happens at 2 levels: + 1) File is read into blocks + 2) Pe block is parsed and loaded + + --AGL */ + +/* Better hope you pass good args.. --AGL */ + +static void +pevent_trigger_load (int *i_penum, char **i_text, char **i_snd) +{ + int penum = *i_penum, len; + char *text = *i_text, *snd = *i_snd; + + if (penum != -1 && text != NULL) + { + len = strlen (text) + 1; + if (pntevts_text[penum]) + free (pntevts_text[penum]); + pntevts_text[penum] = malloc (len); + memcpy (pntevts_text[penum], text, len); + } + + if (text) + free (text); + if (snd) + free (snd); + *i_text = NULL; + *i_snd = NULL; + *i_penum = 0; +} + +static int +pevent_find (char *name, int *i_i) +{ + int i = *i_i, j; + + j = i + 1; + while (1) + { + if (j == NUM_XP) + j = 0; + if (strcmp (te[j].name, name) == 0) + { + *i_i = j; + return j; + } + if (j == i) + return -1; + j++; + } +} + +int +pevent_load (char *filename) +{ + /* AGL, I've changed this file and pevent_save, could you please take a look at + * the changes and possibly modify them to suit you + * //David H + */ + char *buf, *ibuf; + int fd, i = 0, pnt = 0; + struct stat st; + char *text = NULL, *snd = NULL; + int penum = 0; + char *ofs; + + if (filename == NULL) + fd = xchat_open_file ("pevents.conf", O_RDONLY, 0, 0); + else + fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH); + + if (fd == -1) + return 1; + if (fstat (fd, &st) != 0) + return 1; + ibuf = malloc (st.st_size); + read (fd, ibuf, st.st_size); + close (fd); + + while (buf_get_line (ibuf, &buf, &pnt, st.st_size)) + { + if (buf[0] == '#') + continue; + if (strlen (buf) == 0) + continue; + + ofs = strchr (buf, '='); + if (!ofs) + continue; + *ofs = 0; + ofs++; + /*if (*ofs == 0) + continue;*/ + + if (strcmp (buf, "event_name") == 0) + { + if (penum >= 0) + pevent_trigger_load (&penum, &text, &snd); + penum = pevent_find (ofs, &i); + continue; + } else if (strcmp (buf, "event_text") == 0) + { + if (text) + free (text); + +#if 0 + /* This allows updating of old strings. We don't use new defaults + if the user has customized the strings (.e.g a text theme). + Hash of the old default is enough to identify and replace it. + This only works in English. */ + + switch (g_str_hash (ofs)) + { + case 0x526743a4: + /* %C08,02 Hostmask PRIV NOTI CHAN CTCP INVI UNIG %O */ + text = strdup (te[XP_TE_IGNOREHEADER].def); + break; + + case 0xe91bc9c2: + /* %C08,02 %O */ + text = strdup (te[XP_TE_IGNOREFOOTER].def); + break; + + case 0x1fbfdf22: + /* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */ + text = strdup (te[XP_TE_DCCFILEERR].def); + break; + + default: + text = strdup (ofs); + } +#else + text = strdup (ofs); +#endif + + continue; + }/* else if (strcmp (buf, "event_sound") == 0) + { + if (snd) + free (snd); + snd = strdup (ofs); + continue; + }*/ + + continue; + } + + pevent_trigger_load (&penum, &text, &snd); + free (ibuf); + return 0; +} + +static void +pevent_check_all_loaded () +{ + int i; + + for (i = 0; i < NUM_XP; i++) + { + if (pntevts_text[i] == NULL) + { + /*printf ("%s\n", te[i].name); + snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of XChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]); + gtkutil_simpledialog(out); */ + /* make-te.c sets this 128 flag (DON'T call gettext() flag) */ + if (te[i].num_args & 128) + pntevts_text[i] = strdup (te[i].def); + else + pntevts_text[i] = strdup (_(te[i].def)); + } + } +} + +void +load_text_events () +{ + memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP)); + memset (&pntevts, 0, sizeof (char *) * (NUM_XP)); + + if (pevent_load (NULL)) + pevent_load_defaults (); + pevent_check_all_loaded (); + pevent_make_pntevts (); +} + +/* + CL: format_event now handles filtering of arguments: + 1) if prefs.stripcolor is set, filter all style control codes from arguments + 2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself +*/ +#define ARG_FLAG(argn) (1 << (argn)) + +void +format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args) +{ + int len, oi, ii, numargs; + char *i, *ar, d, a, done_all = FALSE; + + i = pntevts[index]; + numargs = te[index].num_args & 0x7f; + + oi = ii = len = d = a = 0; + o[0] = 0; + + if (i == NULL) + return; + + while (done_all == FALSE) + { + d = i[ii++]; + switch (d) + { + case 0: + memcpy (&len, &(i[ii]), sizeof (int)); + ii += sizeof (int); + if (oi + len > sizeofo) + { + printf ("Overflow in display_event (%s)\n", i); + o[0] = 0; + return; + } + memcpy (&(o[oi]), &(i[ii]), len); + oi += len; + ii += len; + break; + case 1: + a = i[ii++]; + if (a > numargs) + { + fprintf (stderr, + "XChat DEBUG: display_event: arg > numargs (%d %d %s)\n", + a, numargs, i); + break; + } + ar = args[(int) a + 1]; + if (ar == NULL) + { + printf ("arg[%d] is NULL in print event\n", a + 1); + } else + { + if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL); + else len = strip_hidden_attribute (ar, &o[oi]); + oi += len; + } + break; + case 2: + o[oi++] = '\n'; + o[oi++] = 0; + done_all = TRUE; + continue; + case 3: +/* if (sess->type == SESS_DIALOG) + { + if (prefs.dialog_indent_nicks) + o[oi++] = '\t'; + else + o[oi++] = ' '; + } else + {*/ + if (prefs.indent_nicks) + o[oi++] = '\t'; + else + o[oi++] = ' '; + /*}*/ + break; + } + } + o[oi] = 0; + if (*o == '\n') + o[0] = 0; +} + +static void +display_event (session *sess, int event, char **args, unsigned int stripcolor_args) +{ + char o[4096]; + format_event (sess, event, args, o, sizeof (o), stripcolor_args); + if (o[0]) + PrintText (sess, o); +} + +int +pevt_build_string (const char *input, char **output, int *max_arg) +{ + struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next; + int clen; + char o[4096], d, *obuf, *i; + int oi, ii, max = -1, len, x; + + len = strlen (input); + i = malloc (len + 1); + memcpy (i, input, len + 1); + check_special_chars (i, TRUE); + + len = strlen (i); + + clen = oi = ii = 0; + + for (;;) + { + if (ii == len) + break; + d = i[ii++]; + if (d != '$') + { + o[oi++] = d; + continue; + } + if (i[ii] == '$') + { + o[oi++] = '$'; + continue; + } + if (oi > 0) + { + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (oi + sizeof (int) + 1); + s->len = oi + sizeof (int) + 1; + clen += oi + sizeof (int) + 1; + s->data[0] = 0; + memcpy (&(s->data[1]), &oi, sizeof (int)); + memcpy (&(s->data[1 + sizeof (int)]), o, oi); + oi = 0; + } + if (ii == len) + { + fe_message ("String ends with a $", FE_MSG_WARN); + return 1; + } + d = i[ii++]; + if (d == 'a') + { /* Hex value */ + x = 0; + if (ii == len) + goto a_len_error; + d = i[ii++]; + d -= '0'; + x = d * 100; + if (ii == len) + goto a_len_error; + d = i[ii++]; + d -= '0'; + x += d * 10; + if (ii == len) + goto a_len_error; + d = i[ii++]; + d -= '0'; + x += d; + if (x > 255) + goto a_range_error; + o[oi++] = x; + continue; + + a_len_error: + fe_message ("String ends in $a", FE_MSG_WARN); + return 1; + a_range_error: + fe_message ("$a value is greater than 255", FE_MSG_WARN); + return 1; + } + if (d == 't') + { + /* Tab - if tabnicks is set then write '\t' else ' ' */ + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (1); + s->len = 1; + clen += 1; + s->data[0] = 3; + + continue; + } + if (d < '1' || d > '9') + { + snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d); + fe_message (o, FE_MSG_WARN); + return 1; + } + d -= '0'; + if (max < d) + max = d; + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (2); + s->len = 2; + clen += 2; + s->data[0] = 1; + s->data[1] = d - 1; + } + if (oi > 0) + { + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (oi + sizeof (int) + 1); + s->len = oi + sizeof (int) + 1; + clen += oi + sizeof (int) + 1; + s->data[0] = 0; + memcpy (&(s->data[1]), &oi, sizeof (int)); + memcpy (&(s->data[1 + sizeof (int)]), o, oi); + oi = 0; + } + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (1); + s->len = 1; + clen += 1; + s->data[0] = 2; + + oi = 0; + s = base; + obuf = malloc (clen); + while (s) + { + next = s->next; + memcpy (&obuf[oi], s->data, s->len); + oi += s->len; + free (s->data); + free (s); + s = next; + } + + free (i); + + if (max_arg) + *max_arg = max; + if (output) + *output = obuf; + + return 0; +} + + +/* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */ +/* also light/dark gray (14/15) */ +/* 5,7,8 are all shades of yellow which happen to look dman near the same */ + +static char rcolors[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 }; + +static int +color_of (char *name) +{ + int i = 0, sum = 0; + + while (name[i]) + sum += name[i++]; + sum %= sizeof (rcolors) / sizeof (char); + return rcolors[sum]; +} + + +/* called by EMIT_SIGNAL macro */ + +void +text_emit (int index, session *sess, char *a, char *b, char *c, char *d) +{ + char *word[PDIWORDS]; + int i; + unsigned int stripcolor_args = (prefs.stripcolor ? 0xFFFFFFFF : 0); + char tbuf[NICKLEN + 4]; + + if (prefs.colorednicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG)) + { + snprintf (tbuf, sizeof (tbuf), "\003%d%s", color_of (a), a); + a = tbuf; + stripcolor_args &= ~ARG_FLAG(1); /* don't strip color from this argument */ + } + + word[0] = te[index].name; + word[1] = (a ? a : "\000"); + word[2] = (b ? b : "\000"); + word[3] = (c ? c : "\000"); + word[4] = (d ? d : "\000"); + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + if (plugin_emit_print (sess, word)) + return; + + /* If a plugin's callback executes "/close", 'sess' may be invalid */ + if (!is_session (sess)) + return; + + switch (index) + { + case XP_TE_JOIN: + case XP_TE_PART: + case XP_TE_PARTREASON: + case XP_TE_QUIT: + /* implement ConfMode / Hide Join and Part Messages */ + if (chanopt_is_set (prefs.confmode, sess->text_hidejoinpart)) + return; + break; + + /* ===Private message=== */ + case XP_TE_PRIVMSG: + case XP_TE_DPRIVMSG: + case XP_TE_PRIVACTION: + case XP_TE_DPRIVACTION: + if (chanopt_is_set_a (prefs.input_beep_priv, sess->alert_beep)) + sound_beep (sess); + if (chanopt_is_set_a (prefs.input_flash_priv, sess->alert_taskbar)) + fe_flash_window (sess); + /* why is this one different? because of plugin-tray.c's hooks! ugly */ + if (sess->alert_tray == SET_ON) + fe_tray_set_icon (FE_ICON_MESSAGE); + break; + + /* ===Highlighted message=== */ + case XP_TE_HCHANACTION: + case XP_TE_HCHANMSG: + if (chanopt_is_set_a (prefs.input_beep_hilight, sess->alert_beep)) + sound_beep (sess); + if (chanopt_is_set_a (prefs.input_flash_hilight, sess->alert_taskbar)) + fe_flash_window (sess); + if (sess->alert_tray == SET_ON) + fe_tray_set_icon (FE_ICON_MESSAGE); + break; + + /* ===Channel message=== */ + case XP_TE_CHANACTION: + case XP_TE_CHANMSG: + if (chanopt_is_set_a (prefs.input_beep_chans, sess->alert_beep)) + sound_beep (sess); + if (chanopt_is_set_a (prefs.input_flash_chans, sess->alert_taskbar)) + fe_flash_window (sess); + if (sess->alert_tray == SET_ON) + fe_tray_set_icon (FE_ICON_MESSAGE); + break; + } + + sound_play_event (index); + display_event (sess, index, word, stripcolor_args); +} + +char * +text_find_format_string (char *name) +{ + int i = 0; + + i = pevent_find (name, &i); + if (i >= 0) + return pntevts_text[i]; + + return NULL; +} + +int +text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d) +{ + int i = 0; + + i = pevent_find (name, &i); + if (i >= 0) + { + text_emit (i, sess, a, b, c, d); + return 1; + } + + return 0; +} + +void +pevent_save (char *fn) +{ + int fd, i; + char buf[1024]; + + if (!fn) + fd = xchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE); + else + fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180, + XOF_FULLPATH | XOF_DOMODE); + if (fd == -1) + { + /* + fe_message ("Error opening config file\n", FALSE); + If we get here when X-Chat is closing the fe-message causes a nice & hard crash + so we have to use perror which doesn't rely on GTK + */ + + perror ("Error opening config file\n"); + return; + } + + for (i = 0; i < NUM_XP; i++) + { + write (fd, buf, snprintf (buf, sizeof (buf), + "event_name=%s\n", te[i].name)); + write (fd, buf, snprintf (buf, sizeof (buf), + "event_text=%s\n\n", pntevts_text[i])); + } + + close (fd); +} + +/* =========================== */ +/* ========== SOUND ========== */ +/* =========================== */ + +char *sound_files[NUM_XP]; + +void +sound_beep (session *sess) +{ + if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0]) + /* user defined beep _file_ */ + sound_play_event (XP_TE_BEEP); + else + /* system beep */ + fe_beep (); +} + +static char * +sound_find_command (void) +{ + /* some sensible unix players. You're bound to have one of them */ + static const char * const progs[] = {"aplay", "esdplay", "soxplay", "artsplay", NULL}; + char *cmd; + int i = 0; + + if (prefs.soundcmd[0]) + return g_strdup (prefs.soundcmd); + + while (progs[i]) + { + cmd = g_find_program_in_path (progs[i]); + if (cmd) + return cmd; + i++; + } + + return NULL; +} + +void +sound_play (const char *file, gboolean quiet) +{ + char buf[512]; + char wavfile[512]; + char *file_fs; + char *cmd; + + /* the pevents GUI editor triggers this after removing a soundfile */ + if (!file[0]) + return; + +#ifdef WIN32 + /* check for fullpath, windows style */ + if (strlen (file) > 3 && + file[1] == ':' && (file[2] == '\\' || file[2] == '/') ) + { + strncpy (wavfile, file, sizeof (wavfile)); + } else +#endif + if (file[0] != '/') + { + snprintf (wavfile, sizeof (wavfile), "%s/%s", prefs.sounddir, file); + } else + { + strncpy (wavfile, file, sizeof (wavfile)); + } + wavfile[sizeof (wavfile) - 1] = 0; /* ensure termination */ + + file_fs = xchat_filename_from_utf8 (wavfile, -1, 0, 0, 0); + if (!file_fs) + return; + + if (access (file_fs, R_OK) == 0) + { + cmd = sound_find_command (); + +#ifdef WIN32 + if (cmd == NULL || strcmp (cmd, "esdplay") == 0) + { + PlaySound (file_fs, NULL, SND_NODEFAULT|SND_FILENAME|SND_ASYNC); + } else +#endif + { + if (cmd) + { + if (strchr (file_fs, ' ')) + snprintf (buf, sizeof (buf), "%s \"%s\"", cmd, file_fs); + else + snprintf (buf, sizeof (buf), "%s %s", cmd, file_fs); + buf[sizeof (buf) - 1] = '\0'; + xchat_exec (buf); + } + } + + if (cmd) + g_free (cmd); + + } else + { + if (!quiet) + { + snprintf (buf, sizeof (buf), _("Cannot read sound file:\n%s"), wavfile); + fe_message (buf, FE_MSG_ERROR); + } + } + + g_free (file_fs); +} + +void +sound_play_event (int i) +{ + if (sound_files[i]) + sound_play (sound_files[i], FALSE); +} + +static void +sound_load_event (char *evt, char *file) +{ + int i = 0; + + if (file[0] && pevent_find (evt, &i) != -1) + { + if (sound_files[i]) + free (sound_files[i]); + sound_files[i] = strdup (file); + } +} + +void +sound_load () +{ + int fd; + char buf[512]; + char evt[128]; + + memset (&sound_files, 0, sizeof (char *) * (NUM_XP)); + + fd = xchat_open_file ("sound.conf", O_RDONLY, 0, 0); + if (fd == -1) + return; + + evt[0] = 0; + while (waitline (fd, buf, sizeof buf, FALSE) != -1) + { + if (strncmp (buf, "event=", 6) == 0) + { + safe_strcpy (evt, buf + 6, sizeof (evt)); + } + else if (strncmp (buf, "sound=", 6) == 0) + { + if (evt[0] != 0) + { + sound_load_event (evt, buf + 6); + evt[0] = 0; + } + } + } + + close (fd); +} + +void +sound_save () +{ + int fd, i; + char buf[512]; + + fd = xchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, + XOF_DOMODE); + if (fd == -1) + return; + + for (i = 0; i < NUM_XP; i++) + { + if (sound_files[i] && sound_files[i][0]) + { + write (fd, buf, snprintf (buf, sizeof (buf), + "event=%s\n", te[i].name)); + write (fd, buf, snprintf (buf, sizeof (buf), + "sound=%s\n\n", sound_files[i])); + } + } + + close (fd); +} diff --git a/src/common/text.h b/src/common/text.h new file mode 100644 index 00000000..150821ae --- /dev/null +++ b/src/common/text.h @@ -0,0 +1,42 @@ +#include "textenums.h" + +#ifndef XCHAT_TEXT_H +#define XCHAT_TEXT_H + +#define EMIT_SIGNAL(i, sess, a, b, c, d, e) text_emit(i, sess, a, b, c, d) + +struct text_event +{ + char *name; + char * const *help; + int num_args; + char *def; +}; + +void scrollback_close (session *sess); +void scrollback_load (session *sess); + +int text_word_check (char *word, int len); +void PrintText (session *sess, char *text); +void PrintTextf (session *sess, char *format, ...); +void log_close (session *sess); +void log_open_or_close (session *sess); +void load_text_events (void); +void pevent_save (char *fn); +int pevt_build_string (const char *input, char **output, int *max_arg); +int pevent_load (char *filename); +void pevent_make_pntevts (void); +void text_emit (int index, session *sess, char *a, char *b, char *c, char *d); +int text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d); +char *text_validate (char **text, int *len); +int get_stamp_str (char *fmt, time_t tim, char **ret); +void format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args); +char *text_find_format_string (char *name); + +void sound_play (const char *file, gboolean quiet); +void sound_play_event (int i); +void sound_beep (session *); +void sound_load (); +void sound_save (); + +#endif diff --git a/src/common/textenums.h b/src/common/textenums.h new file mode 100644 index 00000000..682a35ef --- /dev/null +++ b/src/common/textenums.h @@ -0,0 +1,76 @@ +/* this file is auto generated, edit textevents.in instead! */ + +enum +{ + XP_TE_ADDNOTIFY, XP_TE_BANLIST, + XP_TE_BANNED, XP_TE_BEEP, + XP_TE_CHANGENICK, XP_TE_CHANACTION, + XP_TE_HCHANACTION, XP_TE_CHANBAN, + XP_TE_CHANDATE, XP_TE_CHANDEHOP, + XP_TE_CHANDEOP, XP_TE_CHANDEVOICE, + XP_TE_CHANEXEMPT, XP_TE_CHANHOP, + XP_TE_CHANINVITE, XP_TE_CHANLISTHEAD, + XP_TE_CHANMSG, XP_TE_CHANMODEGEN, + XP_TE_CHANMODES, XP_TE_HCHANMSG, + XP_TE_CHANNOTICE, XP_TE_CHANOP, + XP_TE_CHANRMEXEMPT, XP_TE_CHANRMINVITE, + XP_TE_CHANRMKEY, XP_TE_CHANRMLIMIT, + XP_TE_CHANSETKEY, XP_TE_CHANSETLIMIT, + XP_TE_CHANUNBAN, XP_TE_CHANVOICE, + XP_TE_CONNECTED, XP_TE_CONNECT, + XP_TE_CONNFAIL, XP_TE_CTCPGEN, + XP_TE_CTCPGENC, XP_TE_CTCPSEND, + XP_TE_CTCPSND, XP_TE_CTCPSNDC, + XP_TE_DCCCHATABORT, XP_TE_DCCCONCHAT, + XP_TE_DCCCHATF, XP_TE_DCCCHATOFFER, + XP_TE_DCCCHATOFFERING, XP_TE_DCCCHATREOFFER, + XP_TE_DCCCONFAIL, XP_TE_DCCGENERICOFFER, + XP_TE_DCCHEAD, XP_TE_MALFORMED, + XP_TE_DCCOFFER, XP_TE_DCCIVAL, + XP_TE_DCCRECVABORT, XP_TE_DCCRECVCOMP, + XP_TE_DCCCONRECV, XP_TE_DCCRECVERR, + XP_TE_DCCFILEERR, XP_TE_DCCRENAME, + XP_TE_DCCRESUMEREQUEST, XP_TE_DCCSENDABORT, + XP_TE_DCCSENDCOMP, XP_TE_DCCCONSEND, + XP_TE_DCCSENDFAIL, XP_TE_DCCSENDOFFER, + XP_TE_DCCSTALL, XP_TE_DCCTOUT, + XP_TE_DELNOTIFY, XP_TE_DISCON, + XP_TE_FOUNDIP, XP_TE_GENMSG, + XP_TE_IGNOREADD, XP_TE_IGNORECHANGE, + XP_TE_IGNOREFOOTER, XP_TE_IGNOREHEADER, + XP_TE_IGNOREREMOVE, XP_TE_IGNOREEMPTY, + XP_TE_INVITE, XP_TE_INVITED, + XP_TE_JOIN, XP_TE_KEYWORD, + XP_TE_KICK, XP_TE_KILL, + XP_TE_MSGSEND, XP_TE_MOTD, + XP_TE_MOTDSKIP, XP_TE_NICKCLASH, + XP_TE_NICKFAIL, XP_TE_NODCC, + XP_TE_NOCHILD, XP_TE_NOTICE, + XP_TE_NOTICESEND, XP_TE_NOTIFYEMPTY, + XP_TE_NOTIFYHEAD, XP_TE_NOTIFYNUMBER, + XP_TE_NOTIFYOFFLINE, XP_TE_NOTIFYONLINE, + XP_TE_OPENDIALOG, XP_TE_PART, + XP_TE_PARTREASON, XP_TE_PINGREP, + XP_TE_PINGTIMEOUT, XP_TE_PRIVACTION, + XP_TE_DPRIVACTION, XP_TE_PRIVMSG, + XP_TE_DPRIVMSG, XP_TE_ALREADYPROCESS, + XP_TE_QUIT, XP_TE_RAWMODES, + XP_TE_WALLOPS, XP_TE_RESOLVINGUSER, + XP_TE_SERVERCONNECTED, XP_TE_SERVERERROR, + XP_TE_SERVERLOOKUP, XP_TE_SERVNOTICE, + XP_TE_SERVTEXT, XP_TE_SSLMESSAGE, + XP_TE_STOPCONNECT, XP_TE_TOPIC, + XP_TE_NEWTOPIC, XP_TE_TOPICDATE, + XP_TE_UKNHOST, XP_TE_USERLIMIT, + XP_TE_USERSONCHAN, XP_TE_WHOIS_AUTH, + XP_TE_WHOIS5, XP_TE_WHOIS2, + XP_TE_WHOIS6, XP_TE_WHOIS_ID, + XP_TE_WHOIS4, XP_TE_WHOIS4T, + XP_TE_WHOIS1, XP_TE_WHOIS_REALHOST, + XP_TE_WHOIS3, XP_TE_WHOIS_SPECIAL, + XP_TE_UJOIN, XP_TE_UKICK, + XP_TE_UPART, XP_TE_UPARTREASON, + XP_TE_UACTION, XP_TE_UINVITE, + XP_TE_UCHANMSG, XP_TE_UCHANGENICK, + NUM_XP +}; diff --git a/src/common/textevents.h b/src/common/textevents.h new file mode 100644 index 00000000..08a54da5 --- /dev/null +++ b/src/common/textevents.h @@ -0,0 +1,424 @@ +/* this file is auto generated, edit textevents.in instead! */ + +const struct text_event te[] = { + +{"Add Notify", pevt_generic_nick_help, 1, +N_("%C22*%O$t$1 added to notify list.")}, + +{"Ban List", pevt_banlist_help, 4, +N_("%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3")}, + +{"Banned", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(You are banned).")}, + +{"Beep", pevt_generic_none_help, 128, +""}, + +{"Change Nick", pevt_changenick_help, 2, +N_("%C22*%O$t$1 is now known as $2")}, + +{"Channel Action", pevt_chanaction_help, 132, +"%C18*$t$1%O $2"}, + +{"Channel Action Hilight", pevt_chanaction_help, 132, +"%C21*%O$t%C21%B$1%O%C21 $2"}, + +{"Channel Ban", pevt_chanban_help, 2, +N_("%C22*%O$t$1 sets ban on $2")}, + +{"Channel Creation", pevt_chandate_help, 2, +N_("%C22*%O$tChannel $1 created on $2")}, + +{"Channel DeHalfOp", pevt_chandehop_help, 2, +N_("%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2")}, + +{"Channel DeOp", pevt_chandeop_help, 2, +N_("%C22*%O$t%C26$1%O removes channel operator status from%C26 $2")}, + +{"Channel DeVoice", pevt_chandevoice_help, 2, +N_("%C22*%O$t%C26$1%O removes voice from%C26 $2")}, + +{"Channel Exempt", pevt_chanexempt_help, 2, +N_("%C22*%O$t$1 sets exempt on $2")}, + +{"Channel Half-Operator", pevt_chanhop_help, 2, +N_("%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2")}, + +{"Channel INVITE", pevt_chaninvite_help, 2, +N_("%C22*%O$t$1 sets invite on $2")}, + +{"Channel List", pevt_generic_none_help, 0, +N_("%UChannel Users Topic")}, + +{"Channel Message", pevt_chanmsg_help, 132, +"%C18%H<%H$4$1%H>%H%O$t$2"}, + +{"Channel Mode Generic", pevt_chanmodegen_help, 4, +N_("%C22*%O$t$1 sets mode $2$3 $4")}, + +{"Channel Modes", pevt_chanmodes_help, 2, +N_("%C22*%O$t%C22Channel $1 modes: $2")}, + +{"Channel Msg Hilight", pevt_chanmsg_help, 132, +"$4%C21%B%H<%H$1%H>%H%O%C21$t$2"}, + +{"Channel Notice", pevt_channotice_help, 131, +"%C28-%C29$1/$2%C28-%O$t$3"}, + +{"Channel Operator", pevt_chanop_help, 2, +N_("%C22*%O$t%C26$1%O gives channel operator status to%C26 $2")}, + +{"Channel Remove Exempt", pevt_chanrmexempt_help, 2, +N_("%C22*%O$t$1 removes exempt on $2")}, + +{"Channel Remove Invite", pevt_chanrminvite_help, 2, +N_("%C22*%O$t$1 removes invite on $2")}, + +{"Channel Remove Keyword", pevt_chanrmkey_help, 1, +N_("%C22*%O$t$1 removes channel keyword")}, + +{"Channel Remove Limit", pevt_chanrmlimit_help, 1, +N_("%C22*%O$t$1 removes user limit")}, + +{"Channel Set Key", pevt_chansetkey_help, 2, +N_("%C22*%O$t$1 sets channel keyword to $2")}, + +{"Channel Set Limit", pevt_chansetlimit_help, 2, +N_("%C22*%O$t$1 sets channel limit to $2")}, + +{"Channel UnBan", pevt_chanunban_help, 2, +N_("%C22*%O$t$1 removes ban on $2")}, + +{"Channel Voice", pevt_chanvoice_help, 2, +N_("%C22*%O$t%C26$1%O gives voice to%C26 $2")}, + +{"Connected", pevt_generic_none_help, 0, +N_("%C22*%O$t%C22Connected. Now logging in...")}, + +{"Connecting", pevt_connect_help, 3, +N_("%C22*%O$t%C22Connecting to $1 ($2) port $3%O...")}, + +{"Connection Failed", pevt_connfail_help, 1, +N_("%C21*%O$t%C21Connection failed. Error: $1")}, + +{"CTCP Generic", pevt_ctcpgen_help, 2, +N_("%C22*%O$tReceived a CTCP $1 from $2")}, + +{"CTCP Generic to Channel", pevt_ctcpgenc_help, 3, +N_("%C22*%O$tReceived a CTCP $1 from $2 (to $3)")}, + +{"CTCP Send", pevt_ctcpsend_help, 2, +N_("%C19>%O$1%C19<%O$tCTCP $2")}, + +{"CTCP Sound", pevt_ctcpsnd_help, 2, +N_("%C22*%O$tReceived a CTCP Sound $1 from $2")}, + +{"CTCP Sound to Channel", pevt_ctcpsnd_help, 3, +N_("%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3)")}, + +{"DCC CHAT Abort", pevt_dccchatabort_help, 1, +N_("%C22*%O$tDCC CHAT to %C26$1%O aborted.")}, + +{"DCC CHAT Connect", pevt_dccchatcon_help, 2, +N_("%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30]")}, + +{"DCC CHAT Failed", pevt_dccchaterr_help, 4, +N_("%C22*%O$tDCC CHAT to %C26$1%O lost ($4).")}, + +{"DCC CHAT Offer", pevt_generic_nick_help, 1, +N_("%C22*%O$tReceived a DCC CHAT offer from $1")}, + +{"DCC CHAT Offering", pevt_generic_nick_help, 1, +N_("%C22*%O$tOffering DCC CHAT to $1")}, + +{"DCC CHAT Reoffer", pevt_generic_nick_help, 1, +N_("%C22*%O$tAlready offering CHAT to $1")}, + +{"DCC Conection Failed", pevt_dccconfail_help, 3, +N_("%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3).")}, + +{"DCC Generic Offer", pevt_dccgenericoffer_help, 2, +N_("%C22*%O$tReceived '$1%O' from $2")}, + +{"DCC Header", pevt_generic_none_help, 0, +N_("%C24,18 Type To/From Status Size Pos File ")}, + +{"DCC Malformed", pevt_malformed_help, 2, +N_("%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2")}, + +{"DCC Offer", pevt_dccoffer_help, 3, +N_("%C22*%O$tOffering%C26 $1%O to%C26 $2")}, + +{"DCC Offer Not Valid", pevt_generic_none_help, 0, +N_("%C22*%O$tNo such DCC offer.")}, + +{"DCC RECV Abort", pevt_dccfileabort_help, 2, +N_("%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted.")}, + +{"DCC RECV Complete", pevt_dccrecvcomp_help, 4, +N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O.")}, + +{"DCC RECV Connect", pevt_dcccon_help, 3, +N_("%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30]")}, + +{"DCC RECV Failed", pevt_dccrecverr_help, 4, +N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4).")}, + +{"DCC RECV File Open Error", pevt_generic_file_help, 2, +N_("%C22*%O$tDCC RECV: Cannot open $1 for writing ($2).")}, + +{"DCC Rename", pevt_dccrename_help, 2, +N_("%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead.")}, + +{"DCC RESUME Request", pevt_dccresumeoffer_help, 3, +N_("%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C.")}, + +{"DCC SEND Abort", pevt_dccfileabort_help, 2, +N_("%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted.")}, + +{"DCC SEND Complete", pevt_dccsendcomp_help, 3, +N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O.")}, + +{"DCC SEND Connect", pevt_dcccon_help, 3, +N_("%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30]")}, + +{"DCC SEND Failed", pevt_dccsendfail_help, 3, +N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3")}, + +{"DCC SEND Offer", pevt_dccsendoffer_help, 4, +N_("%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes)")}, + +{"DCC Stall", pevt_dccstall_help, 3, +N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting.")}, + +{"DCC Timeout", pevt_dccstall_help, 3, +N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting.")}, + +{"Delete Notify", pevt_generic_nick_help, 1, +N_("%C22*%O$t$1 deleted from notify list.")}, + +{"Disconnected", pevt_discon_help, 1, +N_("%C22*%O$tDisconnected ($1).")}, + +{"Found IP", pevt_foundip_help, 1, +N_("%C22*%O$tFound your IP: [$1]")}, + +{"Generic Message", pevt_genmsg_help, 130, +"$1$t$2"}, + +{"Ignore Add", pevt_ignoreaddremove_help, 1, +N_("%O%C26$1%O added to ignore list.")}, + +{"Ignore Changed", pevt_ignoreaddremove_help, 1, +N_("Ignore on %C26$1%O changed.")}, + +{"Ignore Footer", pevt_generic_none_help, 0, +N_("%C24,18 ")}, + +{"Ignore Header", pevt_generic_none_help, 0, +N_("%C24,18 Hostmask PRIV NOTI CHAN CTCP DCC INVI UNIG ")}, + +{"Ignore Remove", pevt_ignoreaddremove_help, 1, +N_("%O%C26$1%O removed from ignore list.")}, + +{"Ignorelist Empty", pevt_generic_none_help, 0, +N_(" Ignore list is empty.")}, + +{"Invite", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only).")}, + +{"Invited", pevt_invited_help, 3, +N_("%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C)")}, + +{"Join", pevt_join_help, 3, +N_("%C19*%O$t%C19%B$1 %B($3) has joined $2")}, + +{"Keyword", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword).")}, + +{"Kick", pevt_kick_help, 4, +N_("%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21)")}, + +{"Killed", pevt_kill_help, 2, +N_("%C22*%O$tYou have been killed by $1 ($2%O%C22)")}, + +{"Message Send", pevt_ctcpsend_help, 130, +"%C19>%O$1%C19<%O$t$2"}, + +{"Motd", pevt_servertext_help, 129, +"%C16*%O$t$1%O"}, + +{"MOTD Skipped", pevt_generic_none_help, 0, +N_("%C22*%O$t%C22MOTD Skipped.")}, + +{"Nick Clash", pevt_nickclash_help, 2, +N_("%C22*%O$t$1 already in use. Retrying with $2...")}, + +{"Nick Failed", pevt_generic_none_help, 0, +N_("%C22*%O$tNickname already in use. Use /NICK to try another.")}, + +{"No DCC", pevt_generic_none_help, 0, +N_("%C22*%O$tNo such DCC.")}, + +{"No Running Process", pevt_generic_none_help, 0, +N_("%C22*%O$tNo process is currently running")}, + +{"Notice", pevt_notice_help, 130, +"%C28-%C29$1%C28-%O$t$2"}, + +{"Notice Send", pevt_ctcpsend_help, 130, +"%C19>%O$1%C19<%O$t$2"}, + +{"Notify Empty", pevt_generic_none_help, 0, +N_("$tNotify list is empty.")}, + +{"Notify Header", pevt_generic_none_help, 0, +N_("%C24,18 %B Notify List ")}, + +{"Notify Number", pevt_notifynumber_help, 1, +N_("%C22*%O$t$1 users in notify list.")}, + +{"Notify Offline", pevt_generic_nick_help, 3, +N_("%C22*%O$tNotify: $1 is offline ($3).")}, + +{"Notify Online", pevt_generic_nick_help, 3, +N_("%C22*%O$tNotify: $1 is online ($3).")}, + +{"Open Dialog", pevt_generic_none_help, 128, +""}, + +{"Part", pevt_part_help, 3, +N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3")}, + +{"Part with Reason", pevt_partreason_help, 4, +N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23)")}, + +{"Ping Reply", pevt_pingrep_help, 2, +N_("%C22*%O$tPing reply from $1: $2 second(s)")}, + +{"Ping Timeout", pevt_pingtimeout_help, 1, +N_("%C22*%O$tNo ping reply for $1 seconds, disconnecting.")}, + +{"Private Action", pevt_privmsg_help, 131, +"%C18**$t$3$1%O $2 %C18**"}, + +{"Private Action to Dialog", pevt_privmsg_help, 131, +"%C18*$t$3$1%O $2"}, + +{"Private Message", pevt_privmsg_help, 131, +"%C28*%C29$3$1%C28*$t%O$2"}, + +{"Private Message to Dialog", pevt_privmsg_help, 131, +"%C18%H<%H$3$1%H>%H%O$t$2"}, + +{"Process Already Running", pevt_generic_none_help, 0, +N_("%C22*%O$tA process is already running")}, + +{"Quit", pevt_quit_help, 3, +N_("%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23)")}, + +{"Raw Modes", pevt_rawmodes_help, 2, +N_("%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30]")}, + +{"Receive Wallops", pevt_privmsg_help, 2, +N_("%C28-%C29$1/Wallops%C28-%O$t$2")}, + +{"Resolving User", pevt_resolvinguser_help, 2, +N_("%C22*%O$tLooking up IP number for%C26 $1%O...")}, + +{"Server Connected", pevt_generic_none_help, 0, +N_("%C22*%O$t%C22Connected.")}, + +{"Server Error", pevt_servererror_help, 129, +"%C22*%O$t$1"}, + +{"Server Lookup", pevt_serverlookup_help, 1, +N_("%C22*%O$t%C22Looking up $1")}, + +{"Server Notice", pevt_servertext_help, 130, +"%C22*%O$t$1"}, + +{"Server Text", pevt_servertext_help, 131, +"%C22*%O$t$1"}, + +{"SSL Message", pevt_sslmessage_help, 130, +"%C22*%O$t$1"}, + +{"Stop Connection", pevt_sconnect_help, 1, +N_("%C22*%O$tStopped previous connection attempt (pid=$1)")}, + +{"Topic", pevt_topic_help, 2, +N_("%C29*%O$t%C29Topic for $1%C %C29is: $2")}, + +{"Topic Change", pevt_newtopic_help, 3, +N_("%C22*%O$t$1 has changed the topic to: $2")}, + +{"Topic Creation", pevt_topicdate_help, 3, +N_("%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3")}, + +{"Unknown Host", pevt_generic_none_help, 0, +N_("%C22*%O$tUnknown host. Maybe you misspelled it?")}, + +{"User Limit", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(User limit reached).")}, + +{"Users On Channel", pevt_usersonchan_help, 2, +N_("%C22*%O$t%C26Users on $1:%C $2")}, + +{"WhoIs Authenticated", pevt_whoisauth_help, 3, +N_("%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3")}, + +{"WhoIs Away Line", pevt_whois5_help, 2, +N_("%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30)")}, + +{"WhoIs Channel/Oper Line", pevt_whois2_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"WhoIs End", pevt_whois6_help, 1, +N_("%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list.")}, + +{"WhoIs Identified", pevt_whoisid_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"WhoIs Idle Line", pevt_whois4_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2")}, + +{"WhoIs Idle Line with Signon", pevt_whois4t_help, 3, +N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3")}, + +{"WhoIs Name Line", pevt_whois1_help, 4, +N_("%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4")}, + +{"WhoIs Real Host", pevt_whoisrealhost_help, 4, +N_("%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3")}, + +{"WhoIs Server Line", pevt_whois3_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"WhoIs Special", pevt_whoisid_help, 3, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"You Join", pevt_join_help, 3, +N_("%C19*%O$t%C19Now talking on $2")}, + +{"You Kicked", pevt_ukick_help, 4, +N_("%C23*$tYou have been kicked from $2 by $3 ($4%O%C23)")}, + +{"You Part", pevt_part_help, 3, +N_("%C23*$tYou have left channel $3")}, + +{"You Part with Reason", pevt_partreason_help, 4, +N_("%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23)")}, + +{"Your Action", pevt_chanaction_help, 131, +"%C18*$t$1%O $2"}, + +{"Your Invitation", pevt_uinvite_help, 3, +N_("%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O)")}, + +{"Your Message", pevt_chanmsg_help, 132, +"%C31%H<%H$4$1%H>%H%O%C30$t$2"}, + +{"Your Nick Changing", pevt_uchangenick_help, 2, +N_("%C22*%O$tYou are now known as $2")}, +}; diff --git a/src/common/textevents.in b/src/common/textevents.in new file mode 100644 index 00000000..5b4ce18c --- /dev/null +++ b/src/common/textevents.in @@ -0,0 +1,840 @@ +Add Notify +XP_TE_ADDNOTIFY +pevt_generic_nick_help +%C22*%O$t$1 added to notify list. +1 + +Ban List +XP_TE_BANLIST +pevt_banlist_help +%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3 +4 + +Banned +XP_TE_BANNED +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(You are banned). +1 + +Beep +XP_TE_BEEP +pevt_generic_none_help + +n0 + +Change Nick +XP_TE_CHANGENICK +pevt_changenick_help +%C22*%O$t$1 is now known as $2 +2 + +Channel Action +XP_TE_CHANACTION +pevt_chanaction_help +%C18*$t$1%O $2 +n4 + +Channel Action Hilight +XP_TE_HCHANACTION +pevt_chanaction_help +%C21*%O$t%C21%B$1%O%C21 $2 +n4 + +Channel Ban +XP_TE_CHANBAN +pevt_chanban_help +%C22*%O$t$1 sets ban on $2 +2 + +Channel Creation +XP_TE_CHANDATE +pevt_chandate_help +%C22*%O$tChannel $1 created on $2 +2 + +Channel DeHalfOp +XP_TE_CHANDEHOP +pevt_chandehop_help +%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2 +2 + +Channel DeOp +XP_TE_CHANDEOP +pevt_chandeop_help +%C22*%O$t%C26$1%O removes channel operator status from%C26 $2 +2 + +Channel DeVoice +XP_TE_CHANDEVOICE +pevt_chandevoice_help +%C22*%O$t%C26$1%O removes voice from%C26 $2 +2 + +Channel Exempt +XP_TE_CHANEXEMPT +pevt_chanexempt_help +%C22*%O$t$1 sets exempt on $2 +2 + +Channel Half-Operator +XP_TE_CHANHOP +pevt_chanhop_help +%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2 +2 + +Channel INVITE +XP_TE_CHANINVITE +pevt_chaninvite_help +%C22*%O$t$1 sets invite on $2 +2 + +Channel List +XP_TE_CHANLISTHEAD +pevt_generic_none_help +%UChannel Users Topic +0 + +Channel Message +XP_TE_CHANMSG +pevt_chanmsg_help +%C18%H<%H$4$1%H>%H%O$t$2 +n4 + +Channel Mode Generic +XP_TE_CHANMODEGEN +pevt_chanmodegen_help +%C22*%O$t$1 sets mode $2$3 $4 +4 + +Channel Modes +XP_TE_CHANMODES +pevt_chanmodes_help +%C22*%O$t%C22Channel $1 modes: $2 +2 + +Channel Msg Hilight +XP_TE_HCHANMSG +pevt_chanmsg_help +$4%C21%B%H<%H$1%H>%H%O%C21$t$2 +n4 + +Channel Notice +XP_TE_CHANNOTICE +pevt_channotice_help +%C28-%C29$1/$2%C28-%O$t$3 +n3 + +Channel Operator +XP_TE_CHANOP +pevt_chanop_help +%C22*%O$t%C26$1%O gives channel operator status to%C26 $2 +2 + +Channel Remove Exempt +XP_TE_CHANRMEXEMPT +pevt_chanrmexempt_help +%C22*%O$t$1 removes exempt on $2 +2 + +Channel Remove Invite +XP_TE_CHANRMINVITE +pevt_chanrminvite_help +%C22*%O$t$1 removes invite on $2 +2 + +Channel Remove Keyword +XP_TE_CHANRMKEY +pevt_chanrmkey_help +%C22*%O$t$1 removes channel keyword +1 + +Channel Remove Limit +XP_TE_CHANRMLIMIT +pevt_chanrmlimit_help +%C22*%O$t$1 removes user limit +1 + +Channel Set Key +XP_TE_CHANSETKEY +pevt_chansetkey_help +%C22*%O$t$1 sets channel keyword to $2 +2 + +Channel Set Limit +XP_TE_CHANSETLIMIT +pevt_chansetlimit_help +%C22*%O$t$1 sets channel limit to $2 +2 + +Channel UnBan +XP_TE_CHANUNBAN +pevt_chanunban_help +%C22*%O$t$1 removes ban on $2 +2 + +Channel Voice +XP_TE_CHANVOICE +pevt_chanvoice_help +%C22*%O$t%C26$1%O gives voice to%C26 $2 +2 + +Connected +XP_TE_CONNECTED +pevt_generic_none_help +%C22*%O$t%C22Connected. Now logging in... +0 + +Connecting +XP_TE_CONNECT +pevt_connect_help +%C22*%O$t%C22Connecting to $1 ($2) port $3%O... +3 + +Connection Failed +XP_TE_CONNFAIL +pevt_connfail_help +%C21*%O$t%C21Connection failed. Error: $1 +1 + +CTCP Generic +XP_TE_CTCPGEN +pevt_ctcpgen_help +%C22*%O$tReceived a CTCP $1 from $2 +2 + +CTCP Generic to Channel +XP_TE_CTCPGENC +pevt_ctcpgenc_help +%C22*%O$tReceived a CTCP $1 from $2 (to $3) +3 + +CTCP Send +XP_TE_CTCPSEND +pevt_ctcpsend_help +%C19>%O$1%C19<%O$tCTCP $2 +2 + +CTCP Sound +XP_TE_CTCPSND +pevt_ctcpsnd_help +%C22*%O$tReceived a CTCP Sound $1 from $2 +2 + +CTCP Sound to Channel +XP_TE_CTCPSNDC +pevt_ctcpsnd_help +%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3) +3 + +DCC CHAT Abort +XP_TE_DCCCHATABORT +pevt_dccchatabort_help +%C22*%O$tDCC CHAT to %C26$1%O aborted. +1 + +DCC CHAT Connect +XP_TE_DCCCONCHAT +pevt_dccchatcon_help +%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30] +2 + +DCC CHAT Failed +XP_TE_DCCCHATF +pevt_dccchaterr_help +%C22*%O$tDCC CHAT to %C26$1%O lost ($4). +4 + +DCC CHAT Offer +XP_TE_DCCCHATOFFER +pevt_generic_nick_help +%C22*%O$tReceived a DCC CHAT offer from $1 +1 + +DCC CHAT Offering +XP_TE_DCCCHATOFFERING +pevt_generic_nick_help +%C22*%O$tOffering DCC CHAT to $1 +1 + +DCC CHAT Reoffer +XP_TE_DCCCHATREOFFER +pevt_generic_nick_help +%C22*%O$tAlready offering CHAT to $1 +1 + +DCC Conection Failed +XP_TE_DCCCONFAIL +pevt_dccconfail_help +%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3). +3 + +DCC Generic Offer +XP_TE_DCCGENERICOFFER +pevt_dccgenericoffer_help +%C22*%O$tReceived '$1%O' from $2 +2 + +DCC Header +XP_TE_DCCHEAD +pevt_generic_none_help +%C24,18 Type To/From Status Size Pos File +0 + +DCC Malformed +XP_TE_MALFORMED +pevt_malformed_help +%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2 +2 + +DCC Offer +XP_TE_DCCOFFER +pevt_dccoffer_help +%C22*%O$tOffering%C26 $1%O to%C26 $2 +3 + +DCC Offer Not Valid +XP_TE_DCCIVAL +pevt_generic_none_help +%C22*%O$tNo such DCC offer. +0 + +DCC RECV Abort +XP_TE_DCCRECVABORT +pevt_dccfileabort_help +%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted. +2 + +DCC RECV Complete +XP_TE_DCCRECVCOMP +pevt_dccrecvcomp_help +%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O. +4 + +DCC RECV Connect +XP_TE_DCCCONRECV +pevt_dcccon_help +%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30] +3 + +DCC RECV Failed +XP_TE_DCCRECVERR +pevt_dccrecverr_help +%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4). +4 + +DCC RECV File Open Error +XP_TE_DCCFILEERR +pevt_generic_file_help +%C22*%O$tDCC RECV: Cannot open $1 for writing ($2). +2 + +DCC Rename +XP_TE_DCCRENAME +pevt_dccrename_help +%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead. +2 + +DCC RESUME Request +XP_TE_DCCRESUMEREQUEST +pevt_dccresumeoffer_help +%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C. +3 + +DCC SEND Abort +XP_TE_DCCSENDABORT +pevt_dccfileabort_help +%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted. +2 + +DCC SEND Complete +XP_TE_DCCSENDCOMP +pevt_dccsendcomp_help +%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O. +3 + +DCC SEND Connect +XP_TE_DCCCONSEND +pevt_dcccon_help +%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30] +3 + +DCC SEND Failed +XP_TE_DCCSENDFAIL +pevt_dccsendfail_help +%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3 +3 + +DCC SEND Offer +XP_TE_DCCSENDOFFER +pevt_dccsendoffer_help +%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes) +4 + +DCC Stall +XP_TE_DCCSTALL +pevt_dccstall_help +%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting. +3 + +DCC Timeout +XP_TE_DCCTOUT +pevt_dccstall_help +%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting. +3 + +Delete Notify +XP_TE_DELNOTIFY +pevt_generic_nick_help +%C22*%O$t$1 deleted from notify list. +1 + +Disconnected +XP_TE_DISCON +pevt_discon_help +%C22*%O$tDisconnected ($1). +1 + +Found IP +XP_TE_FOUNDIP +pevt_foundip_help +%C22*%O$tFound your IP: [$1] +1 + +Generic Message +XP_TE_GENMSG +pevt_genmsg_help +$1$t$2 +n2 + +Ignore Add +XP_TE_IGNOREADD +pevt_ignoreaddremove_help +%O%C26$1%O added to ignore list. +1 + +Ignore Changed +XP_TE_IGNORECHANGE +pevt_ignoreaddremove_help +Ignore on %C26$1%O changed. +1 + +Ignore Footer +XP_TE_IGNOREFOOTER +pevt_generic_none_help +%C24,18 +0 + +Ignore Header +XP_TE_IGNOREHEADER +pevt_generic_none_help +%C24,18 Hostmask PRIV NOTI CHAN CTCP DCC INVI UNIG +0 + +Ignore Remove +XP_TE_IGNOREREMOVE +pevt_ignoreaddremove_help +%O%C26$1%O removed from ignore list. +1 + +Ignorelist Empty +XP_TE_IGNOREEMPTY +pevt_generic_none_help + Ignore list is empty. +0 + +Invite +XP_TE_INVITE +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only). +1 + +Invited +XP_TE_INVITED +pevt_invited_help +%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C) +3 + +Join +XP_TE_JOIN +pevt_join_help +%C19*%O$t%C19%B$1 %B($3) has joined $2 +3 + +Keyword +XP_TE_KEYWORD +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword). +1 + +Kick +XP_TE_KICK +pevt_kick_help +%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21) +4 + +Killed +XP_TE_KILL +pevt_kill_help +%C22*%O$tYou have been killed by $1 ($2%O%C22) +2 + +Message Send +XP_TE_MSGSEND +pevt_ctcpsend_help +%C19>%O$1%C19<%O$t$2 +n2 + +Motd +XP_TE_MOTD +pevt_servertext_help +%C16*%O$t$1%O +n1 + +MOTD Skipped +XP_TE_MOTDSKIP +pevt_generic_none_help +%C22*%O$t%C22MOTD Skipped. +0 + +Nick Clash +XP_TE_NICKCLASH +pevt_nickclash_help +%C22*%O$t$1 already in use. Retrying with $2... +2 + +Nick Failed +XP_TE_NICKFAIL +pevt_generic_none_help +%C22*%O$tNickname already in use. Use /NICK to try another. +0 + +No DCC +XP_TE_NODCC +pevt_generic_none_help +%C22*%O$tNo such DCC. +0 + +No Running Process +XP_TE_NOCHILD +pevt_generic_none_help +%C22*%O$tNo process is currently running +0 + +Notice +XP_TE_NOTICE +pevt_notice_help +%C28-%C29$1%C28-%O$t$2 +n2 + +Notice Send +XP_TE_NOTICESEND +pevt_ctcpsend_help +%C19>%O$1%C19<%O$t$2 +n2 + +Notify Empty +XP_TE_NOTIFYEMPTY +pevt_generic_none_help +$tNotify list is empty. +0 + +Notify Header +XP_TE_NOTIFYHEAD +pevt_generic_none_help +%C24,18 %B Notify List +0 + +Notify Number +XP_TE_NOTIFYNUMBER +pevt_notifynumber_help +%C22*%O$t$1 users in notify list. +1 + +Notify Offline +XP_TE_NOTIFYOFFLINE +pevt_generic_nick_help +%C22*%O$tNotify: $1 is offline ($3). +3 + +Notify Online +XP_TE_NOTIFYONLINE +pevt_generic_nick_help +%C22*%O$tNotify: $1 is online ($3). +3 + +Open Dialog +XP_TE_OPENDIALOG +pevt_generic_none_help + +n0 + +Part +XP_TE_PART +pevt_part_help +%C23*%O$t%C23$1 (%O%C23$2) has left $3 +3 + +Part with Reason +XP_TE_PARTREASON +pevt_partreason_help +%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23) +4 + +Ping Reply +XP_TE_PINGREP +pevt_pingrep_help +%C22*%O$tPing reply from $1: $2 second(s) +2 + +Ping Timeout +XP_TE_PINGTIMEOUT +pevt_pingtimeout_help +%C22*%O$tNo ping reply for $1 seconds, disconnecting. +1 + +Private Action +XP_TE_PRIVACTION +pevt_privmsg_help +%C18**$t$3$1%O $2 %C18** +n3 + +Private Action to Dialog +XP_TE_DPRIVACTION +pevt_privmsg_help +%C18*$t$3$1%O $2 +n3 + +Private Message +XP_TE_PRIVMSG +pevt_privmsg_help +%C28*%C29$3$1%C28*$t%O$2 +n3 + +Private Message to Dialog +XP_TE_DPRIVMSG +pevt_privmsg_help +%C18%H<%H$3$1%H>%H%O$t$2 +n3 + +Process Already Running +XP_TE_ALREADYPROCESS +pevt_generic_none_help +%C22*%O$tA process is already running +0 + +Quit +XP_TE_QUIT +pevt_quit_help +%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23) +3 + +Raw Modes +XP_TE_RAWMODES +pevt_rawmodes_help +%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30] +2 + +Receive Wallops +XP_TE_WALLOPS +pevt_privmsg_help +%C28-%C29$1/Wallops%C28-%O$t$2 +2 + +Resolving User +XP_TE_RESOLVINGUSER +pevt_resolvinguser_help +%C22*%O$tLooking up IP number for%C26 $1%O... +2 + +Server Connected +XP_TE_SERVERCONNECTED +pevt_generic_none_help +%C22*%O$t%C22Connected. +0 + +Server Error +XP_TE_SERVERERROR +pevt_servererror_help +%C22*%O$t$1 +n1 + +Server Lookup +XP_TE_SERVERLOOKUP +pevt_serverlookup_help +%C22*%O$t%C22Looking up $1 +1 + +Server Notice +XP_TE_SERVNOTICE +pevt_servertext_help +%C22*%O$t$1 +n2 + +Server Text +XP_TE_SERVTEXT +pevt_servertext_help +%C22*%O$t$1 +n3 + +SSL Message +XP_TE_SSLMESSAGE +pevt_sslmessage_help +%C22*%O$t$1 +n2 + +Stop Connection +XP_TE_STOPCONNECT +pevt_sconnect_help +%C22*%O$tStopped previous connection attempt (pid=$1) +1 + +Topic +XP_TE_TOPIC +pevt_topic_help +%C29*%O$t%C29Topic for $1%C %C29is: $2 +2 + +Topic Change +XP_TE_NEWTOPIC +pevt_newtopic_help +%C22*%O$t$1 has changed the topic to: $2 +3 + +Topic Creation +XP_TE_TOPICDATE +pevt_topicdate_help +%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3 +3 + +Unknown Host +XP_TE_UKNHOST +pevt_generic_none_help +%C22*%O$tUnknown host. Maybe you misspelled it? +0 + +User Limit +XP_TE_USERLIMIT +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(User limit reached). +1 + +Users On Channel +XP_TE_USERSONCHAN +pevt_usersonchan_help +%C22*%O$t%C26Users on $1:%C $2 +2 + +WhoIs Authenticated +XP_TE_WHOIS_AUTH +pevt_whoisauth_help +%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3 +3 + +WhoIs Away Line +XP_TE_WHOIS5 +pevt_whois5_help +%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30) +2 + +WhoIs Channel/Oper Line +XP_TE_WHOIS2 +pevt_whois2_help +%C22*%O$t%C28[%O$1%C28]%O $2 +2 + +WhoIs End +XP_TE_WHOIS6 +pevt_whois6_help +%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list. +1 + +WhoIs Identified +XP_TE_WHOIS_ID +pevt_whoisid_help +%C22*%O$t%C28[%O$1%C28]%O $2 +2 + +WhoIs Idle Line +XP_TE_WHOIS4 +pevt_whois4_help +%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2 +2 + +WhoIs Idle Line with Signon +XP_TE_WHOIS4T +pevt_whois4t_help +%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3 +3 + +WhoIs Name Line +XP_TE_WHOIS1 +pevt_whois1_help +%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4 +4 + +WhoIs Real Host +XP_TE_WHOIS_REALHOST +pevt_whoisrealhost_help +%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3 +4 + +WhoIs Server Line +XP_TE_WHOIS3 +pevt_whois3_help +%C22*%O$t%C28[%O$1%C28]%O $2 +2 + +WhoIs Special +XP_TE_WHOIS_SPECIAL +pevt_whoisid_help +%C22*%O$t%C28[%O$1%C28]%O $2 +3 + +You Join +XP_TE_UJOIN +pevt_join_help +%C19*%O$t%C19Now talking on $2 +3 + +You Kicked +XP_TE_UKICK +pevt_ukick_help +%C23*$tYou have been kicked from $2 by $3 ($4%O%C23) +4 + +You Part +XP_TE_UPART +pevt_part_help +%C23*$tYou have left channel $3 +3 + +You Part with Reason +XP_TE_UPARTREASON +pevt_partreason_help +%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23) +4 + +Your Action +XP_TE_UACTION +pevt_chanaction_help +%C18*$t$1%O $2 +n3 + +Your Invitation +XP_TE_UINVITE +pevt_uinvite_help +%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O) +3 + +Your Message +XP_TE_UCHANMSG +pevt_chanmsg_help +%C31%H<%H$4$1%H>%H%O%C30$t$2 +n4 + +Your Nick Changing +XP_TE_UCHANGENICK +pevt_uchangenick_help +%C22*%O$tYou are now known as $2 +2 + diff --git a/src/common/tree.c b/src/common/tree.c new file mode 100644 index 00000000..1627bd98 --- /dev/null +++ b/src/common/tree.c @@ -0,0 +1,215 @@ +/* +This is used for quick userlist insertion and lookup. It's not really +a tree, but it could be :) +*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "tree.h" + +#define ARRAY_GROW 32 + +struct _tree +{ + int elements; + int array_size; + void **array; + tree_cmp_func *cmp; + void *data; +}; + +tree * +tree_new (tree_cmp_func *cmp, void *data) +{ + tree *t = calloc (1, sizeof (tree)); + t->cmp = cmp; + t->data = data; + return t; +} + +void +tree_destroy (tree *t) +{ + if (t) + { + if (t->array) + free (t->array); + free (t); + } +} + +static int +tree_find_insertion_pos (tree *t, void *key, int *done) +{ + int c, u, l, idx; + + if (t->elements < 1) + { + *done = 1; + t->array[0] = key; + t->elements++; + return 0; + } + + if (t->elements < 2) + { + *done = 1; + c = t->cmp (key, t->array[0], t->data); + if (c == 0) + return -1; + t->elements++; + if (c > 0) + { + t->array[1] = key; + return 1; + } + t->array[1] = t->array[0]; + t->array[0] = key; + return 0; + } + + *done = 0; + + c = t->cmp (key, t->array[0], t->data); + if (c < 0) + return 0; /* prepend */ + + c = t->cmp (key, t->array[t->elements - 1], t->data); + if (c > 0) + return t->elements; /* append */ + + l = 0; + u = t->elements - 1; + while (1) + { + idx = (l + u) / 2; + c = t->cmp (key, t->array[idx], t->data); + + if (0 > c) + u = idx; + else if (0 < c && 0 > t->cmp (key, t->array[idx+1], t->data)) + return idx + 1; + else if (c == 0) + return -1; + else + l = idx + 1; + } +} + +static void +tree_insert_at_pos (tree *t, void *key, int pos) +{ + int post_bytes; + + /* append is easy */ + if (pos != t->elements) + { + post_bytes = (t->elements - pos) * sizeof (void *); + memmove (&t->array[pos + 1], &t->array[pos], post_bytes); + } + + t->array[pos] = key; + t->elements++; +} + +static void * +mybsearch (const void *key, void **array, size_t nmemb, + int (*compar) (const void *, const void *, void *data), void *data, int *pos) +{ + int l, u, idx; + int comparison; + + l = 0; + u = nmemb; + while (l < u) + { + idx = (l + u) / 2; + comparison = (*compar) (key, array[idx], data); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + { + *pos = idx; + return array[idx]; + } + } + + return NULL; +} + +void * +tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos) +{ + if (!t || !t->array) + return NULL; + + return mybsearch (key, &t->array[0], t->elements, cmp, data, pos); +} + +static void +tree_remove_at_pos (tree *t, int pos) +{ + int post_bytes; + + t->elements--; + if (pos != t->elements) + { + post_bytes = (t->elements - pos) * sizeof (void *); + memmove (&t->array[pos], &t->array[pos + 1], post_bytes); + } +} + +int +tree_remove (tree *t, void *key, int *pos) +{ + void *data; + + data = tree_find (t, key, t->cmp, t->data, pos); + if (!data) + return 0; + + tree_remove_at_pos (t, *pos); + return 1; +} + +void +tree_foreach (tree *t, tree_traverse_func *func, void *data) +{ + int j; + + if (!t || !t->array) + return; + + for (j = 0; j < t->elements; j++) + { + if (!func (t->array[j], data)) + break; + } +} + +int +tree_insert (tree *t, void *key) +{ + int pos, done; + + if (!t) + return -1; + + if (t->array_size < t->elements + 1) + { + int new_size = t->array_size + ARRAY_GROW; + + t->array = realloc (t->array, sizeof (void *) * new_size); + t->array_size = new_size; + } + + pos = tree_find_insertion_pos (t, key, &done); + if (!done && pos != -1) + tree_insert_at_pos (t, key, pos); + + return pos; +} diff --git a/src/common/tree.h b/src/common/tree.h new file mode 100644 index 00000000..b1b66aa9 --- /dev/null +++ b/src/common/tree.h @@ -0,0 +1,16 @@ +#ifndef XCHAT_TREE_H +#define XCHAT_TREE_H + +typedef struct _tree tree; + +typedef int (tree_cmp_func) (const void *keya, const void *keyb, void *data); +typedef int (tree_traverse_func) (const void *key, void *data); + +tree *tree_new (tree_cmp_func *cmp, void *data); +void tree_destroy (tree *t); +void *tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos); +int tree_remove (tree *t, void *key, int *pos); +void tree_foreach (tree *t, tree_traverse_func *func, void *data); +int tree_insert (tree *t, void *key); + +#endif diff --git a/src/common/url.c b/src/common/url.c new file mode 100644 index 00000000..92aeab0a --- /dev/null +++ b/src/common/url.c @@ -0,0 +1,280 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "xchat.h" +#include "cfgfiles.h" +#include "fe.h" +#include "tree.h" +#include "url.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +void *url_tree = NULL; + + +static int +url_free (char *url, void *data) +{ + free (url); + return TRUE; +} + +void +url_clear (void) +{ + tree_foreach (url_tree, (tree_traverse_func *)url_free, NULL); + tree_destroy (url_tree); + url_tree = NULL; +} + +static int +url_save_cb (char *url, FILE *fd) +{ + fprintf (fd, "%s\n", url); + return TRUE; +} + +void +url_save (const char *fname, const char *mode, gboolean fullpath) +{ + FILE *fd; + + if (fullpath) + fd = xchat_fopen_file (fname, mode, XOF_FULLPATH); + else + fd = xchat_fopen_file (fname, mode, 0); + if (fd == NULL) + return; + + tree_foreach (url_tree, (tree_traverse_func *)url_save_cb, fd); + fclose (fd); +} + +void +url_autosave (void) +{ + url_save ("url.save", "a", FALSE); +} + +static int +url_find (char *urltext) +{ + int pos; + + if (tree_find (url_tree, urltext, (tree_cmp_func *)strcasecmp, NULL, &pos)) + return 1; + return 0; +} + +static void +url_add (char *urltext, int len) +{ + char *data = malloc (len + 1); + if (!data) + return; + memcpy (data, urltext, len); + data[len] = 0; + + if (data[len - 1] == '.') /* chop trailing dot */ + { + len--; + data[len] = 0; + } + if (data[len - 1] == ')') /* chop trailing ) */ + data[len - 1] = 0; + + if (url_find (data)) + { + free (data); + return; + } + + if (!url_tree) + url_tree = tree_new ((tree_cmp_func *)strcasecmp, NULL); + + tree_insert (url_tree, data); + fe_url_add (data); +} + +/* check if a word is clickable. This is called on mouse motion events, so + keep it FAST! This new version was found to be almost 3x faster than + 2.4.4 release. */ + +int +url_check_word (char *word, int len) +{ +#define D(x) (x), ((sizeof (x)) - 1) + static const struct { + const char *s; + int len; + } + prefix[] = { + { D("irc.") }, + { D("ftp.") }, + { D("www.") }, + { D("irc://") }, + { D("ftp://") }, + { D("http://") }, + { D("https://") }, + { D("file://") }, + { D("rtsp://") }, + { D("ut2004://") }, + }, + suffix[] = { + { D(".org") }, + { D(".net") }, + { D(".com") }, + { D(".edu") }, + { D(".html") }, + { D(".info") }, + { D(".name") }, + }; +#undef D + const char *at, *dot; + int i, dots; + + if (len > 1 && word[1] == '#' && strchr("@+^%*#", word[0])) + return WORD_CHANNEL; + + if ((word[0] == '#' || word[0] == '&') && word[1] != '#' && word[1] != 0) + return WORD_CHANNEL; + + for (i = 0; i < G_N_ELEMENTS(prefix); i++) + { + int l; + + l = prefix[i].len; + if (len > l) + { + int j; + + /* This is pretty much strncasecmp(). */ + for (j = 0; j < l; j++) + { + unsigned char c = word[j]; + if (tolower(c) != prefix[i].s[j]) + break; + } + if (j == l) + return WORD_URL; + } + } + + at = strchr (word, '@'); /* check for email addy */ + dot = strrchr (word, '.'); + if (at && dot) + { + if (at < dot) + { + if (strchr (word, '*')) + return WORD_HOST; + else + return WORD_EMAIL; + } + } + + /* check if it's an IP number */ + dots = 0; + for (i = 0; i < len; i++) + { + if (word[i] == '.' && i > 0) + dots++; /* allow 127.0.0.1:80 */ + else if (!isdigit ((unsigned char) word[i]) && word[i] != ':') + { + dots = 0; + break; + } + } + if (dots == 3) + return WORD_HOST; + + if (len > 5) + { + for (i = 0; i < G_N_ELEMENTS(suffix); i++) + { + int l; + + l = suffix[i].len; + if (len > l) + { + const unsigned char *p = &word[len - l]; + int j; + + /* This is pretty much strncasecmp(). */ + for (j = 0; j < l; j++) + { + if (tolower(p[j]) != suffix[i].s[j]) + break; + } + if (j == l) + return WORD_HOST; + } + } + + if (word[len - 3] == '.' && + isalpha ((unsigned char) word[len - 2]) && + isalpha ((unsigned char) word[len - 1])) + return WORD_HOST; + } + + return 0; +} + +void +url_check_line (char *buf, int len) +{ + char *po = buf; + char *start; + int wlen; + + if (buf[0] == ':' && buf[1] != 0) + po++; + + start = po; + + /* check each "word" (space separated) */ + while (1) + { + switch (po[0]) + { + case 0: + case ' ': + wlen = po - start; + if (wlen > 2) + { + if (url_check_word (start, wlen) == WORD_URL) + { + url_add (start, wlen); + } + } + if (po[0] == 0) + return; + po++; + start = po; + break; + + default: + po++; + } + } +} diff --git a/src/common/url.h b/src/common/url.h new file mode 100644 index 00000000..487a2007 --- /dev/null +++ b/src/common/url.h @@ -0,0 +1,19 @@ +#ifndef XCHAT_URL_H +#define XCHAT_URL_H + +extern void *url_tree; + +#define WORD_URL 1 +#define WORD_NICK 2 +#define WORD_CHANNEL 3 +#define WORD_HOST 4 +#define WORD_EMAIL 5 +#define WORD_DIALOG -1 + +void url_clear (void); +void url_save (const char *fname, const char *mode, gboolean fullpath); +void url_autosave (void); +int url_check_word (char *word, int len); +void url_check_line (char *buf, int len); + +#endif diff --git a/src/common/userlist.c b/src/common/userlist.c new file mode 100644 index 00000000..18ac4bed --- /dev/null +++ b/src/common/userlist.c @@ -0,0 +1,454 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "xchat.h" +#include "modes.h" +#include "fe.h" +#include "notify.h" +#include "tree.h" +#include "xchatc.h" +#include "util.h" + + +static int +nick_cmp_az_ops (server *serv, struct User *user1, struct User *user2) +{ + unsigned int access1 = user1->access; + unsigned int access2 = user2->access; + int pos; + + if (access1 != access2) + { + for (pos = 0; pos < USERACCESS_SIZE; pos++) + { + if ((access1&(1<<pos)) && (access2&(1<<pos))) + break; + if ((access1&(1<<pos)) && !(access2&(1<<pos))) + return -1; + if (!(access1&(1<<pos)) && (access2&(1<<pos))) + return 1; + } + } + + return serv->p_cmp (user1->nick, user2->nick); +} + +static int +nick_cmp_alpha (struct User *user1, struct User *user2, server *serv) +{ + return serv->p_cmp (user1->nick, user2->nick); +} + +static int +nick_cmp (struct User *user1, struct User *user2, server *serv) +{ + switch (prefs.userlist_sort) + { + case 0: + return nick_cmp_az_ops (serv, user1, user2); + case 1: + return serv->p_cmp (user1->nick, user2->nick); + case 2: + return -1 * nick_cmp_az_ops (serv, user1, user2); + case 3: + return -1 * serv->p_cmp (user1->nick, user2->nick); + default: + return -1; + } +} + +/* + insert name in appropriate place in linked list. Returns row number or: + -1: duplicate +*/ + +static int +userlist_insertname (session *sess, struct User *newuser) +{ + if (!sess->usertree) + { + sess->usertree = tree_new ((tree_cmp_func *)nick_cmp, sess->server); + sess->usertree_alpha = tree_new ((tree_cmp_func *)nick_cmp_alpha, sess->server); + } + + tree_insert (sess->usertree_alpha, newuser); + return tree_insert (sess->usertree, newuser); +} + +void +userlist_set_away (struct session *sess, char *nick, unsigned int away) +{ + struct User *user; + + user = userlist_find (sess, nick); + if (user) + { + if (user->away != away) + { + user->away = away; + /* rehash GUI */ + fe_userlist_rehash (sess, user); + if (away) + fe_userlist_update (sess, user); + } + } +} + +int +userlist_add_hostname (struct session *sess, char *nick, char *hostname, + char *realname, char *servername, unsigned int away) +{ + struct User *user; + + user = userlist_find (sess, nick); + if (user) + { + if (!user->hostname && hostname) + user->hostname = strdup (hostname); + if (!user->realname && realname) + user->realname = strdup (realname); + if (!user->servername && servername) + user->servername = strdup (servername); + + if (away != 0xff) + { + if (prefs.showhostname_in_userlist || user->away != away) + { + user->away = away; + fe_userlist_rehash (sess, user); + } + user->away = away; + } + + fe_userlist_update (sess, user); + + return 1; + } + return 0; +} + +static int +free_user (struct User *user, gpointer data) +{ + if (user->realname) + free (user->realname); + if (user->hostname) + free (user->hostname); + if (user->servername) + free (user->servername); + free (user); + + return TRUE; +} + +void +userlist_free (session *sess) +{ + tree_foreach (sess->usertree, (tree_traverse_func *)free_user, NULL); + tree_destroy (sess->usertree); + tree_destroy (sess->usertree_alpha); + + sess->usertree = NULL; + sess->usertree_alpha = NULL; + sess->me = NULL; + + sess->ops = 0; + sess->hops = 0; + sess->voices = 0; + sess->total = 0; +} + +void +userlist_clear (session *sess) +{ + fe_userlist_clear (sess); + userlist_free (sess); + fe_userlist_numbers (sess); +} + +static int +find_cmp (const char *name, struct User *user, server *serv) +{ + return serv->p_cmp ((char *)name, user->nick); +} + +struct User * +userlist_find (struct session *sess, char *name) +{ + int pos; + + if (sess->usertree_alpha) + return tree_find (sess->usertree_alpha, name, + (tree_cmp_func *)find_cmp, sess->server, &pos); + + return NULL; +} + +struct User * +userlist_find_global (struct server *serv, char *name) +{ + struct User *user; + session *sess; + GSList *list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == serv) + { + user = userlist_find (sess, name); + if (user) + return user; + } + list = list->next; + } + return 0; +} + +static void +update_counts (session *sess, struct User *user, char prefix, + int level, int offset) +{ + switch (prefix) + { + case '@': + user->op = level; + sess->ops += offset; + break; + case '%': + user->hop = level; + sess->hops += offset; + break; + case '+': + user->voice = level; + sess->voices += offset; + break; + } +} + +void +userlist_update_mode (session *sess, char *name, char mode, char sign) +{ + int access; + int offset = 0; + int level; + int pos; + char prefix; + struct User *user; + + user = userlist_find (sess, name); + if (!user) + return; + + /* remove from binary trees, before we loose track of it */ + tree_remove (sess->usertree, user, &pos); + tree_remove (sess->usertree_alpha, user, &pos); + + /* which bit number is affected? */ + access = mode_access (sess->server, mode, &prefix); + + if (sign == '+') + { + level = TRUE; + if (!(user->access & (1 << access))) + { + offset = 1; + user->access |= (1 << access); + } + } else + { + level = FALSE; + if (user->access & (1 << access)) + { + offset = -1; + user->access &= ~(1 << access); + } + } + + /* now what is this users highest prefix? e.g. @ for ops */ + user->prefix[0] = get_nick_prefix (sess->server, user->access); + + /* update the various counts using the CHANGED prefix only */ + update_counts (sess, user, prefix, level, offset); + + /* insert it back into its new place */ + tree_insert (sess->usertree_alpha, user); + pos = tree_insert (sess->usertree, user); + + /* let GTK move it too */ + fe_userlist_move (sess, user, pos); + fe_userlist_numbers (sess); +} + +int +userlist_change (struct session *sess, char *oldname, char *newname) +{ + struct User *user = userlist_find (sess, oldname); + int pos; + + if (user) + { + tree_remove (sess->usertree, user, &pos); + tree_remove (sess->usertree_alpha, user, &pos); + + safe_strcpy (user->nick, newname, NICKLEN); + + tree_insert (sess->usertree_alpha, user); + + fe_userlist_move (sess, user, tree_insert (sess->usertree, user)); + fe_userlist_numbers (sess); + + return 1; + } + + return 0; +} + +int +userlist_remove (struct session *sess, char *name) +{ + struct User *user; + int pos; + + user = userlist_find (sess, name); + if (!user) + return FALSE; + + if (user->voice) + sess->voices--; + if (user->op) + sess->ops--; + if (user->hop) + sess->hops--; + sess->total--; + fe_userlist_numbers (sess); + fe_userlist_remove (sess, user); + + if (user == sess->me) + sess->me = NULL; + + tree_remove (sess->usertree, user, &pos); + tree_remove (sess->usertree_alpha, user, &pos); + free_user (user, NULL); + + return TRUE; +} + +void +userlist_add (struct session *sess, char *name, char *hostname) +{ + struct User *user; + int row, prefix_chars; + unsigned int acc; + + acc = nick_access (sess->server, name, &prefix_chars); + + notify_set_online (sess->server, name + prefix_chars); + + user = malloc (sizeof (struct User)); + memset (user, 0, sizeof (struct User)); + + user->access = acc; + + /* assume first char is the highest level nick prefix */ + if (prefix_chars) + user->prefix[0] = name[0]; + + /* add it to our linked list */ + if (hostname) + user->hostname = strdup (hostname); + safe_strcpy (user->nick, name + prefix_chars, NICKLEN); + /* is it me? */ + if (!sess->server->p_cmp (user->nick, sess->server->nick)) + user->me = TRUE; + row = userlist_insertname (sess, user); + + /* duplicate? some broken servers trigger this */ + if (row == -1) + { + if (user->hostname) + free (user->hostname); + free (user); + return; + } + + sess->total++; + + /* most ircds don't support multiple modechars infront of the nickname + for /NAMES - though they should. */ + while (prefix_chars) + { + update_counts (sess, user, name[0], TRUE, 1); + name++; + prefix_chars--; + } + + if (user->me) + sess->me = user; + + fe_userlist_insert (sess, user, row, FALSE); + fe_userlist_numbers (sess); +} + +static int +rehash_cb (struct User *user, session *sess) +{ + fe_userlist_rehash (sess, user); + return TRUE; +} + +void +userlist_rehash (session *sess) +{ + tree_foreach (sess->usertree_alpha, (tree_traverse_func *)rehash_cb, sess); +} + +static int +flat_cb (struct User *user, GSList **list) +{ + *list = g_slist_prepend (*list, user); + return TRUE; +} + +GSList * +userlist_flat_list (session *sess) +{ + GSList *list = NULL; + + tree_foreach (sess->usertree_alpha, (tree_traverse_func *)flat_cb, &list); + return g_slist_reverse (list); +} + +static int +double_cb (struct User *user, GList **list) +{ + *list = g_list_prepend(*list, user); + return TRUE; +} + +GList * +userlist_double_list(session *sess) +{ + GList *list = NULL; + + tree_foreach (sess->usertree_alpha, (tree_traverse_func *)double_cb, &list); + return list; +} diff --git a/src/common/userlist.h b/src/common/userlist.h new file mode 100644 index 00000000..28831acd --- /dev/null +++ b/src/common/userlist.h @@ -0,0 +1,41 @@ +#include <time.h> + +#ifndef XCHAT_USERLIST_H +#define XCHAT_USERLIST_H + +struct User +{ + char nick[NICKLEN]; + char *hostname; + char *realname; + char *servername; + time_t lasttalk; + unsigned int access; /* axs bit field */ + char prefix[2]; /* @ + % */ + unsigned int op:1; + unsigned int hop:1; + unsigned int voice:1; + unsigned int me:1; + unsigned int away:1; + unsigned int selected:1; +}; + +#define USERACCESS_SIZE 12 + +int userlist_add_hostname (session *sess, char *nick, + char *hostname, char *realname, + char *servername, unsigned int away); +void userlist_set_away (session *sess, char *nick, unsigned int away); +struct User *userlist_find (session *sess, char *name); +struct User *userlist_find_global (server *serv, char *name); +void userlist_clear (session *sess); +void userlist_free (session *sess); +void userlist_add (session *sess, char *name, char *hostname); +int userlist_remove (session *sess, char *name); +int userlist_change (session *sess, char *oldname, char *newname); +void userlist_update_mode (session *sess, char *name, char mode, char sign); +GSList *userlist_flat_list (session *sess); +GList *userlist_double_list (session *sess); +void userlist_rehash (session *sess); + +#endif diff --git a/src/common/util.c b/src/common/util.c new file mode 100644 index 00000000..49517ec4 --- /dev/null +++ b/src/common/util.c @@ -0,0 +1,1729 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 + */ + +#define __APPLE_API_STRICT_CONFORMANCE + +#define _FILE_OFFSET_BITS 64 +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef WIN32 +#include <sys/timeb.h> +#include <process.h> +#else +#include <sys/types.h> +#include <pwd.h> +#include <sys/time.h> +#include <sys/utsname.h> +#endif +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include "xchat.h" +#include "xchatc.h" +#include <glib/gmarkup.h> +#include <ctype.h> +#include "util.h" +#include "../../config.h" + +#define WANTSOCKET +#include "inet.h" + +#if defined (USING_FREEBSD) || defined (__APPLE__) +#include <sys/sysctl.h> +#endif +#ifdef SOCKS +#include <socks.h> +#endif + +#ifndef HAVE_SNPRINTF +#define snprintf g_snprintf +#endif + +#ifdef USE_DEBUG + +#undef free +#undef malloc +#undef realloc +#undef strdup + +int current_mem_usage; + +struct mem_block +{ + char *file; + void *buf; + int size; + int line; + int total; + struct mem_block *next; +}; + +struct mem_block *mroot = NULL; + +void * +xchat_malloc (int size, char *file, int line) +{ + void *ret; + struct mem_block *new; + + current_mem_usage += size; + ret = malloc (size); + if (!ret) + { + printf ("Out of memory! (%d)\n", current_mem_usage); + exit (255); + } + + new = malloc (sizeof (struct mem_block)); + new->buf = ret; + new->size = size; + new->next = mroot; + new->line = line; + new->file = strdup (file); + mroot = new; + + printf ("%s:%d Malloc'ed %d bytes, now \033[35m%d\033[m\n", file, line, + size, current_mem_usage); + + return ret; +} + +void * +xchat_realloc (char *old, int len, char *file, int line) +{ + char *ret; + + ret = xchat_malloc (len, file, line); + if (ret) + { + strcpy (ret, old); + xchat_dfree (old, file, line); + } + return ret; +} + +void * +xchat_strdup (char *str, char *file, int line) +{ + void *ret; + struct mem_block *new; + int size; + + size = strlen (str) + 1; + current_mem_usage += size; + ret = malloc (size); + if (!ret) + { + printf ("Out of memory! (%d)\n", current_mem_usage); + exit (255); + } + strcpy (ret, str); + + new = malloc (sizeof (struct mem_block)); + new->buf = ret; + new->size = size; + new->next = mroot; + new->line = line; + new->file = strdup (file); + mroot = new; + + printf ("%s:%d strdup (\"%-.40s\") size: %d, total: \033[35m%d\033[m\n", + file, line, str, size, current_mem_usage); + + return ret; +} + +void +xchat_mem_list (void) +{ + struct mem_block *cur, *p; + GSList *totals = 0; + GSList *list; + + cur = mroot; + while (cur) + { + list = totals; + while (list) + { + p = list->data; + if (p->line == cur->line && + strcmp (p->file, cur->file) == 0) + { + p->total += p->size; + break; + } + list = list->next; + } + if (!list) + { + cur->total = cur->size; + totals = g_slist_prepend (totals, cur); + } + cur = cur->next; + } + + fprintf (stderr, "file line size num total\n"); + list = totals; + while (list) + { + cur = list->data; + fprintf (stderr, "%-15.15s %6d %6d %6d %6d\n", cur->file, cur->line, + cur->size, cur->total/cur->size, cur->total); + list = list->next; + } +} + +void +xchat_dfree (void *buf, char *file, int line) +{ + struct mem_block *cur, *last; + + if (buf == NULL) + { + printf ("%s:%d \033[33mTried to free NULL\033[m\n", file, line); + return; + } + + last = NULL; + cur = mroot; + while (cur) + { + if (buf == cur->buf) + break; + last = cur; + cur = cur->next; + } + if (cur == NULL) + { + printf ("%s:%d \033[31mTried to free unknown block %lx!\033[m\n", + file, line, (unsigned long) buf); + /* abort(); */ + free (buf); + return; + } + current_mem_usage -= cur->size; + printf ("%s:%d Free'ed %d bytes, usage now \033[35m%d\033[m\n", + file, line, cur->size, current_mem_usage); + if (last) + last->next = cur->next; + else + mroot = cur->next; + free (cur->file); + free (cur); +} + +#define malloc(n) xchat_malloc(n, __FILE__, __LINE__) +#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__) +#define free(n) xchat_dfree(n, __FILE__, __LINE__) +#define strdup(n) xchat_strdup(n, __FILE__, __LINE__) + +#endif /* MEMORY_DEBUG */ + +char * +file_part (char *file) +{ + char *filepart = file; + if (!file) + return ""; + while (1) + { + switch (*file) + { + case 0: + return (filepart); + case '/': +#ifdef WIN32 + case '\\': +#endif + filepart = file + 1; + break; + } + file++; + } +} + +void +path_part (char *file, char *path, int pathlen) +{ + unsigned char t; + char *filepart = file_part (file); + t = *filepart; + *filepart = 0; + safe_strcpy (path, file, pathlen); + *filepart = t; +} + +char * /* like strstr(), but nocase */ +nocasestrstr (const char *s, const char *wanted) +{ + register const int len = strlen (wanted); + + if (len == 0) + return (char *)s; + while (rfc_tolower(*s) != rfc_tolower(*wanted) || strncasecmp (s, wanted, len)) + if (*s++ == '\0') + return (char *)NULL; + return (char *)s; +} + +char * +errorstring (int err) +{ + switch (err) + { + case -1: + return ""; + case 0: + return _("Remote host closed socket"); +#ifndef WIN32 + } +#else + case WSAECONNREFUSED: + return _("Connection refused"); + case WSAENETUNREACH: + case WSAEHOSTUNREACH: + return _("No route to host"); + case WSAETIMEDOUT: + return _("Connection timed out"); + case WSAEADDRNOTAVAIL: + return _("Cannot assign that address"); + case WSAECONNRESET: + return _("Connection reset by peer"); + } + + /* can't use strerror() on Winsock errors! */ + if (err >= WSABASEERR) + { + static char tbuf[384]; + OSVERSIONINFO osvi; + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + GetVersionEx (&osvi); + + /* FormatMessage works on WSA*** errors starting from Win2000 */ + if (osvi.dwMajorVersion >= 5) + { + if (FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + tbuf, sizeof (tbuf), NULL)) + { + int len; + char *utf; + + tbuf[sizeof (tbuf) - 1] = 0; + len = strlen (tbuf); + if (len >= 2) + tbuf[len - 2] = 0; /* remove the cr-lf */ + + /* now convert to utf8 */ + utf = g_locale_to_utf8 (tbuf, -1, 0, 0, 0); + if (utf) + { + safe_strcpy (tbuf, utf, sizeof (tbuf)); + g_free (utf); + return tbuf; + } + } + } /* ! if (osvi.dwMajorVersion >= 5) */ + + /* fallback to error number */ + sprintf (tbuf, "%s %d", _("Error"), err); + return tbuf; + } /* ! if (err >= WSABASEERR) */ +#endif /* ! WIN32 */ + + return strerror (err); +} + +int +waitline (int sok, char *buf, int bufsize, int use_recv) +{ + int i = 0; + + while (1) + { + if (use_recv) + { + if (recv (sok, &buf[i], 1, 0) < 1) + return -1; + } else + { + if (read (sok, &buf[i], 1) < 1) + return -1; + } + if (buf[i] == '\n' || bufsize == i + 1) + { + buf[i] = 0; + return i; + } + i++; + } +} + +/* checks for "~" in a file and expands */ + +char * +expand_homedir (char *file) +{ +#ifndef WIN32 + char *ret, *user; + struct passwd *pw; + + if (*file == '~') + { + if (file[1] != '\0' && file[1] != '/') + { + user = strdup(file); + if (strchr(user,'/') != NULL) + *(strchr(user,'/')) = '\0'; + if ((pw = getpwnam(user + 1)) == NULL) + { + free(user); + return strdup(file); + } + free(user); + user = strchr(file, '/') != NULL ? strchr(file,'/') : file; + ret = malloc(strlen(user) + strlen(pw->pw_dir) + 1); + strcpy(ret, pw->pw_dir); + strcat(ret, user); + } + else + { + ret = malloc (strlen (file) + strlen (g_get_home_dir ()) + 1); + sprintf (ret, "%s%s", g_get_home_dir (), file + 1); + } + return ret; + } +#endif + return strdup (file); +} + +gchar * +strip_color (const char *text, int len, int flags) +{ + char *new_str; + + if (len == -1) + len = strlen (text); + + new_str = g_malloc (len + 2); + strip_color2 (text, len, new_str, flags); + + if (flags & STRIP_ESCMARKUP) + { + char *esc = g_markup_escape_text (new_str, -1); + g_free (new_str); + return esc; + } + + return new_str; +} + +/* CL: strip_color2 strips src and writes the output at dst; pass the same pointer + in both arguments to strip in place. */ +int +strip_color2 (const char *src, int len, char *dst, int flags) +{ + int rcol = 0, bgcol = 0; + char *start = dst; + + if (len == -1) len = strlen (src); + while (len-- > 0) + { + if (rcol > 0 && (isdigit ((unsigned char)*src) || + (*src == ',' && isdigit ((unsigned char)src[1]) && !bgcol))) + { + if (src[1] != ',') rcol--; + if (*src == ',') + { + rcol = 2; + bgcol = 1; + } + } else + { + rcol = bgcol = 0; + switch (*src) + { + case '\003': /*ATTR_COLOR: */ + if (!(flags & STRIP_COLOR)) goto pass_char; + rcol = 2; + break; + case HIDDEN_CHAR: /* CL: invisible text (for event formats only) */ /* this takes care of the topic */ + if (!(flags & STRIP_HIDDEN)) goto pass_char; + break; + case '\007': /*ATTR_BEEP: */ + case '\017': /*ATTR_RESET: */ + case '\026': /*ATTR_REVERSE: */ + case '\002': /*ATTR_BOLD: */ + case '\037': /*ATTR_UNDERLINE: */ + case '\035': /*ATTR_ITALICS: */ + if (!(flags & STRIP_ATTRIB)) goto pass_char; + break; + default: + pass_char: + *dst++ = *src; + } + } + src++; + } + *dst = 0; + + return (int) (dst - start); +} + +int +strip_hidden_attribute (char *src, char *dst) +{ + int len = 0; + while (*src != '\000') + { + if (*src != HIDDEN_CHAR) + { + *dst++ = *src; + len++; + } + src++; + } + return len; +} + +#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__) + +static void +get_cpu_info (double *mhz, int *cpus) +{ + +#ifdef USING_LINUX + + char buf[256]; + int fh; + + *mhz = 0; + *cpus = 0; + + fh = open ("/proc/cpuinfo", O_RDONLY); /* linux 2.2+ only */ + if (fh == -1) + { + *cpus = 1; + return; + } + + while (1) + { + if (waitline (fh, buf, sizeof buf, FALSE) < 0) + break; + if (!strncmp (buf, "cycle frequency [Hz]\t:", 22)) /* alpha */ + { + *mhz = atoi (buf + 23) / 1000000; + } else if (!strncmp (buf, "cpu MHz\t\t:", 10)) /* i386 */ + { + *mhz = atof (buf + 11) + 0.5; + } else if (!strncmp (buf, "clock\t\t:", 8)) /* PPC */ + { + *mhz = atoi (buf + 9); + } else if (!strncmp (buf, "processor\t", 10)) + { + (*cpus)++; + } + } + close (fh); + if (!*cpus) + *cpus = 1; + +#endif +#ifdef USING_FREEBSD + + int mib[2], ncpu; + u_long freq; + size_t len; + + freq = 0; + *mhz = 0; + *cpus = 0; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + + len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); + + len = sizeof(freq); + sysctlbyname("machdep.tsc_freq", &freq, &len, NULL, 0); + + *cpus = ncpu; + *mhz = (freq / 1000000); + +#endif +#ifdef __APPLE__ + + int mib[2], ncpu; + unsigned long long freq; + size_t len; + + freq = 0; + *mhz = 0; + *cpus = 0; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + + len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); + + len = sizeof(freq); + sysctlbyname("hw.cpufrequency", &freq, &len, NULL, 0); + + *cpus = ncpu; + *mhz = (freq / 1000000); + +#endif + +} +#endif + +#ifdef WIN32 + +static int +get_mhz (void) +{ + HKEY hKey; + int result, data, dataSize; + + if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Hardware\\Description\\System\\" + "CentralProcessor\\0", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) + { + dataSize = sizeof (data); + result = RegQueryValueEx (hKey, "~MHz", 0, 0, (LPBYTE)&data, &dataSize); + RegCloseKey (hKey); + if (result == ERROR_SUCCESS) + return data; + } + return 0; /* fails on Win9x */ +} + +char * +get_cpu_str (void) +{ + static char verbuf[64]; + OSVERSIONINFO osvi; + SYSTEM_INFO si; + double mhz; + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + GetVersionEx (&osvi); + GetSystemInfo (&si); + + mhz = get_mhz (); + if (mhz) + { + double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz; + const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz"; + sprintf (verbuf, "Windows %ld.%ld [i%d86/%.2f%s]", + osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel, + cpuspeed, cpuspeedstr); + } else + sprintf (verbuf, "Windows %ld.%ld [i%d86]", + osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel); + + return verbuf; +} + +#else + +char * +get_cpu_str (void) +{ +#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__) + double mhz; +#endif + int cpus = 1; + struct utsname un; + static char *buf = NULL; + + if (buf) + return buf; + + buf = malloc (128); + + uname (&un); + +#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__) + get_cpu_info (&mhz, &cpus); + if (mhz) + { + double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz; + const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz"; + snprintf (buf, 128, + (cpus == 1) ? "%s %s [%s/%.2f%s]" : "%s %s [%s/%.2f%s/SMP]", + un.sysname, un.release, un.machine, + cpuspeed, cpuspeedstr); + } else +#endif + snprintf (buf, 128, + (cpus == 1) ? "%s %s [%s]" : "%s %s [%s/SMP]", + un.sysname, un.release, un.machine); + + return buf; +} + +#endif + +int +buf_get_line (char *ibuf, char **buf, int *position, int len) +{ + int pos = *position, spos = pos; + + if (pos == len) + return 0; + + while (ibuf[pos++] != '\n') + { + if (pos == len) + return 0; + } + pos--; + ibuf[pos] = 0; + *buf = &ibuf[spos]; + pos++; + *position = pos; + return 1; +} + +int match(const char *mask, const char *string) +{ + register const char *m = mask, *s = string; + register char ch; + const char *bm, *bs; /* Will be reg anyway on a decent CPU/compiler */ + + /* Process the "head" of the mask, if any */ + while ((ch = *m++) && (ch != '*')) + switch (ch) + { + case '\\': + if (*m == '?' || *m == '*') + ch = *m++; + default: + if (rfc_tolower(*s) != rfc_tolower(ch)) + return 0; + case '?': + if (!*s++) + return 0; + }; + if (!ch) + return !(*s); + + /* We got a star: quickly find if/where we match the next char */ +got_star: + bm = m; /* Next try rollback here */ + while ((ch = *m++)) + switch (ch) + { + case '?': + if (!*s++) + return 0; + case '*': + bm = m; + continue; /* while */ + case '\\': + if (*m == '?' || *m == '*') + ch = *m++; + default: + goto break_while; /* C is structured ? */ + }; +break_while: + if (!ch) + return 1; /* mask ends with '*', we got it */ + ch = rfc_tolower(ch); + while (rfc_tolower(*s++) != ch) + if (!*s) + return 0; + bs = s; /* Next try start from here */ + + /* Check the rest of the "chunk" */ + while ((ch = *m++)) + { + switch (ch) + { + case '*': + goto got_star; + case '\\': + if (*m == '?' || *m == '*') + ch = *m++; + default: + if (rfc_tolower(*s) != rfc_tolower(ch)) + { + if (!*s) + return 0; + m = bm; + s = bs; + goto got_star; + }; + case '?': + if (!*s++) + return 0; + }; + }; + if (*s) + { + m = bm; + s = bs; + goto got_star; + }; + return 1; +} + +void +for_files (char *dirname, char *mask, void callback (char *file)) +{ + DIR *dir; + struct dirent *ent; + char *buf; + + dir = opendir (dirname); + if (dir) + { + while ((ent = readdir (dir))) + { + if (strcmp (ent->d_name, ".") && strcmp (ent->d_name, "..")) + { + if (match (mask, ent->d_name)) + { + buf = malloc (strlen (dirname) + strlen (ent->d_name) + 2); + sprintf (buf, "%s/%s", dirname, ent->d_name); + callback (buf); + free (buf); + } + } + } + closedir (dir); + } +} + +/*void +tolowerStr (char *str) +{ + while (*str) + { + *str = rfc_tolower (*str); + str++; + } +}*/ + +typedef struct +{ + char *code, *country; +} domain_t; + +static int +country_compare (const void *a, const void *b) +{ + return strcasecmp (a, ((domain_t *)b)->code); +} + +static const domain_t domain[] = +{ + {"AC", N_("Ascension Island") }, + {"AD", N_("Andorra") }, + {"AE", N_("United Arab Emirates") }, + {"AF", N_("Afghanistan") }, + {"AG", N_("Antigua and Barbuda") }, + {"AI", N_("Anguilla") }, + {"AL", N_("Albania") }, + {"AM", N_("Armenia") }, + {"AN", N_("Netherlands Antilles") }, + {"AO", N_("Angola") }, + {"AQ", N_("Antarctica") }, + {"AR", N_("Argentina") }, + {"ARPA", N_("Reverse DNS") }, + {"AS", N_("American Samoa") }, + {"AT", N_("Austria") }, + {"ATO", N_("Nato Fiel") }, + {"AU", N_("Australia") }, + {"AW", N_("Aruba") }, + {"AX", N_("Aland Islands") }, + {"AZ", N_("Azerbaijan") }, + {"BA", N_("Bosnia and Herzegovina") }, + {"BB", N_("Barbados") }, + {"BD", N_("Bangladesh") }, + {"BE", N_("Belgium") }, + {"BF", N_("Burkina Faso") }, + {"BG", N_("Bulgaria") }, + {"BH", N_("Bahrain") }, + {"BI", N_("Burundi") }, + {"BIZ", N_("Businesses"), }, + {"BJ", N_("Benin") }, + {"BM", N_("Bermuda") }, + {"BN", N_("Brunei Darussalam") }, + {"BO", N_("Bolivia") }, + {"BR", N_("Brazil") }, + {"BS", N_("Bahamas") }, + {"BT", N_("Bhutan") }, + {"BV", N_("Bouvet Island") }, + {"BW", N_("Botswana") }, + {"BY", N_("Belarus") }, + {"BZ", N_("Belize") }, + {"CA", N_("Canada") }, + {"CC", N_("Cocos Islands") }, + {"CD", N_("Democratic Republic of Congo") }, + {"CF", N_("Central African Republic") }, + {"CG", N_("Congo") }, + {"CH", N_("Switzerland") }, + {"CI", N_("Cote d'Ivoire") }, + {"CK", N_("Cook Islands") }, + {"CL", N_("Chile") }, + {"CM", N_("Cameroon") }, + {"CN", N_("China") }, + {"CO", N_("Colombia") }, + {"COM", N_("Internic Commercial") }, + {"CR", N_("Costa Rica") }, + {"CS", N_("Serbia and Montenegro") }, + {"CU", N_("Cuba") }, + {"CV", N_("Cape Verde") }, + {"CX", N_("Christmas Island") }, + {"CY", N_("Cyprus") }, + {"CZ", N_("Czech Republic") }, + {"DE", N_("Germany") }, + {"DJ", N_("Djibouti") }, + {"DK", N_("Denmark") }, + {"DM", N_("Dominica") }, + {"DO", N_("Dominican Republic") }, + {"DZ", N_("Algeria") }, + {"EC", N_("Ecuador") }, + {"EDU", N_("Educational Institution") }, + {"EE", N_("Estonia") }, + {"EG", N_("Egypt") }, + {"EH", N_("Western Sahara") }, + {"ER", N_("Eritrea") }, + {"ES", N_("Spain") }, + {"ET", N_("Ethiopia") }, + {"EU", N_("European Union") }, + {"FI", N_("Finland") }, + {"FJ", N_("Fiji") }, + {"FK", N_("Falkland Islands") }, + {"FM", N_("Micronesia") }, + {"FO", N_("Faroe Islands") }, + {"FR", N_("France") }, + {"GA", N_("Gabon") }, + {"GB", N_("Great Britain") }, + {"GD", N_("Grenada") }, + {"GE", N_("Georgia") }, + {"GF", N_("French Guiana") }, + {"GG", N_("British Channel Isles") }, + {"GH", N_("Ghana") }, + {"GI", N_("Gibraltar") }, + {"GL", N_("Greenland") }, + {"GM", N_("Gambia") }, + {"GN", N_("Guinea") }, + {"GOV", N_("Government") }, + {"GP", N_("Guadeloupe") }, + {"GQ", N_("Equatorial Guinea") }, + {"GR", N_("Greece") }, + {"GS", N_("S. Georgia and S. Sandwich Isles") }, + {"GT", N_("Guatemala") }, + {"GU", N_("Guam") }, + {"GW", N_("Guinea-Bissau") }, + {"GY", N_("Guyana") }, + {"HK", N_("Hong Kong") }, + {"HM", N_("Heard and McDonald Islands") }, + {"HN", N_("Honduras") }, + {"HR", N_("Croatia") }, + {"HT", N_("Haiti") }, + {"HU", N_("Hungary") }, + {"ID", N_("Indonesia") }, + {"IE", N_("Ireland") }, + {"IL", N_("Israel") }, + {"IM", N_("Isle of Man") }, + {"IN", N_("India") }, + {"INFO", N_("Informational") }, + {"INT", N_("International") }, + {"IO", N_("British Indian Ocean Territory") }, + {"IQ", N_("Iraq") }, + {"IR", N_("Iran") }, + {"IS", N_("Iceland") }, + {"IT", N_("Italy") }, + {"JE", N_("Jersey") }, + {"JM", N_("Jamaica") }, + {"JO", N_("Jordan") }, + {"JP", N_("Japan") }, + {"KE", N_("Kenya") }, + {"KG", N_("Kyrgyzstan") }, + {"KH", N_("Cambodia") }, + {"KI", N_("Kiribati") }, + {"KM", N_("Comoros") }, + {"KN", N_("St. Kitts and Nevis") }, + {"KP", N_("North Korea") }, + {"KR", N_("South Korea") }, + {"KW", N_("Kuwait") }, + {"KY", N_("Cayman Islands") }, + {"KZ", N_("Kazakhstan") }, + {"LA", N_("Laos") }, + {"LB", N_("Lebanon") }, + {"LC", N_("Saint Lucia") }, + {"LI", N_("Liechtenstein") }, + {"LK", N_("Sri Lanka") }, + {"LR", N_("Liberia") }, + {"LS", N_("Lesotho") }, + {"LT", N_("Lithuania") }, + {"LU", N_("Luxembourg") }, + {"LV", N_("Latvia") }, + {"LY", N_("Libya") }, + {"MA", N_("Morocco") }, + {"MC", N_("Monaco") }, + {"MD", N_("Moldova") }, + {"MED", N_("United States Medical") }, + {"MG", N_("Madagascar") }, + {"MH", N_("Marshall Islands") }, + {"MIL", N_("Military") }, + {"MK", N_("Macedonia") }, + {"ML", N_("Mali") }, + {"MM", N_("Myanmar") }, + {"MN", N_("Mongolia") }, + {"MO", N_("Macau") }, + {"MP", N_("Northern Mariana Islands") }, + {"MQ", N_("Martinique") }, + {"MR", N_("Mauritania") }, + {"MS", N_("Montserrat") }, + {"MT", N_("Malta") }, + {"MU", N_("Mauritius") }, + {"MV", N_("Maldives") }, + {"MW", N_("Malawi") }, + {"MX", N_("Mexico") }, + {"MY", N_("Malaysia") }, + {"MZ", N_("Mozambique") }, + {"NA", N_("Namibia") }, + {"NC", N_("New Caledonia") }, + {"NE", N_("Niger") }, + {"NET", N_("Internic Network") }, + {"NF", N_("Norfolk Island") }, + {"NG", N_("Nigeria") }, + {"NI", N_("Nicaragua") }, + {"NL", N_("Netherlands") }, + {"NO", N_("Norway") }, + {"NP", N_("Nepal") }, + {"NR", N_("Nauru") }, + {"NU", N_("Niue") }, + {"NZ", N_("New Zealand") }, + {"OM", N_("Oman") }, + {"ORG", N_("Internic Non-Profit Organization") }, + {"PA", N_("Panama") }, + {"PE", N_("Peru") }, + {"PF", N_("French Polynesia") }, + {"PG", N_("Papua New Guinea") }, + {"PH", N_("Philippines") }, + {"PK", N_("Pakistan") }, + {"PL", N_("Poland") }, + {"PM", N_("St. Pierre and Miquelon") }, + {"PN", N_("Pitcairn") }, + {"PR", N_("Puerto Rico") }, + {"PS", N_("Palestinian Territory") }, + {"PT", N_("Portugal") }, + {"PW", N_("Palau") }, + {"PY", N_("Paraguay") }, + {"QA", N_("Qatar") }, + {"RE", N_("Reunion") }, + {"RO", N_("Romania") }, + {"RPA", N_("Old School ARPAnet") }, + {"RU", N_("Russian Federation") }, + {"RW", N_("Rwanda") }, + {"SA", N_("Saudi Arabia") }, + {"SB", N_("Solomon Islands") }, + {"SC", N_("Seychelles") }, + {"SD", N_("Sudan") }, + {"SE", N_("Sweden") }, + {"SG", N_("Singapore") }, + {"SH", N_("St. Helena") }, + {"SI", N_("Slovenia") }, + {"SJ", N_("Svalbard and Jan Mayen Islands") }, + {"SK", N_("Slovak Republic") }, + {"SL", N_("Sierra Leone") }, + {"SM", N_("San Marino") }, + {"SN", N_("Senegal") }, + {"SO", N_("Somalia") }, + {"SR", N_("Suriname") }, + {"ST", N_("Sao Tome and Principe") }, + {"SU", N_("Former USSR") }, + {"SV", N_("El Salvador") }, + {"SY", N_("Syria") }, + {"SZ", N_("Swaziland") }, + {"TC", N_("Turks and Caicos Islands") }, + {"TD", N_("Chad") }, + {"TF", N_("French Southern Territories") }, + {"TG", N_("Togo") }, + {"TH", N_("Thailand") }, + {"TJ", N_("Tajikistan") }, + {"TK", N_("Tokelau") }, + {"TL", N_("East Timor") }, + {"TM", N_("Turkmenistan") }, + {"TN", N_("Tunisia") }, + {"TO", N_("Tonga") }, + {"TP", N_("East Timor") }, + {"TR", N_("Turkey") }, + {"TT", N_("Trinidad and Tobago") }, + {"TV", N_("Tuvalu") }, + {"TW", N_("Taiwan") }, + {"TZ", N_("Tanzania") }, + {"UA", N_("Ukraine") }, + {"UG", N_("Uganda") }, + {"UK", N_("United Kingdom") }, + {"US", N_("United States of America") }, + {"UY", N_("Uruguay") }, + {"UZ", N_("Uzbekistan") }, + {"VA", N_("Vatican City State") }, + {"VC", N_("St. Vincent and the Grenadines") }, + {"VE", N_("Venezuela") }, + {"VG", N_("British Virgin Islands") }, + {"VI", N_("US Virgin Islands") }, + {"VN", N_("Vietnam") }, + {"VU", N_("Vanuatu") }, + {"WF", N_("Wallis and Futuna Islands") }, + {"WS", N_("Samoa") }, + {"YE", N_("Yemen") }, + {"YT", N_("Mayotte") }, + {"YU", N_("Yugoslavia") }, + {"ZA", N_("South Africa") }, + {"ZM", N_("Zambia") }, + {"ZW", N_("Zimbabwe") }, +}; + +char * +country (char *hostname) +{ + char *p; + domain_t *dom; + + if (!hostname || !*hostname || isdigit ((unsigned char) hostname[strlen (hostname) - 1])) + return _("Unknown"); + if ((p = strrchr (hostname, '.'))) + p++; + else + p = hostname; + + dom = bsearch (p, domain, sizeof (domain) / sizeof (domain_t), + sizeof (domain_t), country_compare); + + if (!dom) + return _("Unknown"); + + return _(dom->country); +} + +void +country_search (char *pattern, void *ud, void (*print)(void *, char *, ...)) +{ + const domain_t *dom; + int i; + + for (i = 0; i < sizeof (domain) / sizeof (domain_t); i++) + { + dom = &domain[i]; + if (match (pattern, dom->country) || match (pattern, _(dom->country))) + { + print (ud, "%s = %s\n", dom->code, _(dom->country)); + } + } +} + +/* I think gnome1.0.x isn't necessarily linked against popt, ah well! */ +/* !!! For now use this inlined function, or it would break fe-text building */ +/* .... will find a better solution later. */ +/*#ifndef USE_GNOME*/ + +/* this is taken from gnome-libs 1.2.4 */ +#define POPT_ARGV_ARRAY_GROW_DELTA 5 + +int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr) { + char * buf, * bufStart, * dst; + const char * src; + char quote = '\0'; + int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA; + char ** argv = malloc(sizeof(*argv) * argvAlloced); + const char ** argv2; + int argc = 0; + int i, buflen; + + buflen = strlen(s) + 1; +/* bufStart = buf = alloca(buflen);*/ + bufStart = buf = malloc (buflen); + memset(buf, '\0', buflen); + + src = s; + argv[argc] = buf; + + while (*src) { + if (quote == *src) { + quote = '\0'; + } else if (quote) { + if (*src == '\\') { + src++; + if (!*src) { + free(argv); + free(bufStart); + return 1; + } + if (*src != quote) *buf++ = '\\'; + } + *buf++ = *src; + /*} else if (isspace((unsigned char) *src)) {*/ + } else if (*src == ' ') { + if (*argv[argc]) { + buf++, argc++; + if (argc == argvAlloced) { + argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA; + argv = realloc(argv, sizeof(*argv) * argvAlloced); + } + argv[argc] = buf; + } + } else switch (*src) { + case '"': + case '\'': + quote = *src; + break; + case '\\': + src++; + if (!*src) { + free(argv); + free(bufStart); + return 1; + } + /* fallthrough */ + default: + *buf++ = *src; + } + + src++; + } + + if (strlen(argv[argc])) { + argc++, buf++; + } + + dst = malloc((argc + 1) * sizeof(*argv) + (buf - bufStart)); + argv2 = (void *) dst; + dst += (argc + 1) * sizeof(*argv); + memcpy((void *)argv2, argv, argc * sizeof(*argv)); + argv2[argc] = NULL; + memcpy(dst, bufStart, buf - bufStart); + + for (i = 0; i < argc; i++) { + argv2[i] = dst + (argv[i] - bufStart); + } + + free(argv); + + *argvPtr = (char **)argv2; /* XXX don't change the API */ + *argcPtr = argc; + + free (bufStart); + + return 0; +} + +int +util_exec (const char *cmd) +{ + int pid; + char **argv; + int argc; + int fd; + + if (my_poptParseArgvString (cmd, &argc, &argv) != 0) + return -1; + +#ifndef WIN32 + pid = fork (); + if (pid == -1) + return -1; + if (pid == 0) + { + /* Now close all open file descriptors except stdin, stdout and stderr */ + for (fd = 3; fd < 1024; fd++) close(fd); + execvp (argv[0], argv); + _exit (0); + } else + { + free (argv); + return pid; + } +#else + spawnvp (_P_DETACH, argv[0], argv); + free (argv); + return 0; +#endif +} + +int +util_execv (char * const argv[]) +{ + int pid, fd; + +#ifndef WIN32 + pid = fork (); + if (pid == -1) + return -1; + if (pid == 0) + { + /* Now close all open file descriptors except stdin, stdout and stderr */ + for (fd = 3; fd < 1024; fd++) close(fd); + execv (argv[0], argv); + _exit (0); + } else + { + return pid; + } +#else + spawnv (_P_DETACH, argv[0], argv); + return 0; +#endif +} + +unsigned long +make_ping_time (void) +{ +#ifndef WIN32 + struct timeval timev; + gettimeofday (&timev, 0); +#else + GTimeVal timev; + g_get_current_time (&timev); +#endif + return (timev.tv_sec - 50000) * 1000000 + timev.tv_usec; +} + + +/************************************************************************ + * This technique was borrowed in part from the source code to + * ircd-hybrid-5.3 to implement case-insensitive string matches which + * are fully compliant with Section 2.2 of RFC 1459, the copyright + * of that code being (C) 1990 Jarkko Oikarinen and under the GPL. + * + * A special thanks goes to Mr. Okarinen for being the one person who + * seems to have ever noticed this section in the original RFC and + * written code for it. Shame on all the rest of you (myself included). + * + * --+ Dagmar d'Surreal + */ + +int +rfc_casecmp (const char *s1, const char *s2) +{ + register unsigned char *str1 = (unsigned char *) s1; + register unsigned char *str2 = (unsigned char *) s2; + register int res; + + while ((res = rfc_tolower (*str1) - rfc_tolower (*str2)) == 0) + { + if (*str1 == '\0') + return 0; + str1++; + str2++; + } + return (res); +} + +int +rfc_ncasecmp (char *str1, char *str2, int n) +{ + register unsigned char *s1 = (unsigned char *) str1; + register unsigned char *s2 = (unsigned char *) str2; + register int res; + + while ((res = rfc_tolower (*s1) - rfc_tolower (*s2)) == 0) + { + s1++; + s2++; + n--; + if (n == 0 || (*s1 == '\0' && *s2 == '\0')) + return 0; + } + return (res); +} + +const unsigned char rfc_tolowertab[] = + { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, + 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')', + '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ':', ';', '<', '=', '>', '?', + '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', + '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', + 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +/*static unsigned char touppertab[] = + { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, + 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')', + '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', + 0x5f, + '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', + 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +};*/ + +/*static int +rename_utf8 (char *oldname, char *newname) +{ + int sav, res; + char *fso, *fsn; + + fso = xchat_filename_from_utf8 (oldname, -1, 0, 0, 0); + if (!fso) + return FALSE; + fsn = xchat_filename_from_utf8 (newname, -1, 0, 0, 0); + if (!fsn) + { + g_free (fso); + return FALSE; + } + + res = rename (fso, fsn); + sav = errno; + g_free (fso); + g_free (fsn); + errno = sav; + return res; +} + +static int +unlink_utf8 (char *fname) +{ + int res; + char *fs; + + fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0); + if (!fs) + return FALSE; + + res = unlink (fs); + g_free (fs); + return res; +}*/ + +static gboolean +file_exists_utf8 (char *fname) +{ + int res; + char *fs; + + fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0); + if (!fs) + return FALSE; + + res = access (fs, F_OK); + g_free (fs); + if (res == 0) + return TRUE; + return FALSE; +} + +static gboolean +copy_file (char *dl_src, char *dl_dest, int permissions) /* FS encoding */ +{ + int tmp_src, tmp_dest; + gboolean ok = FALSE; + char dl_tmp[4096]; + int return_tmp, return_tmp2; + + if ((tmp_src = open (dl_src, O_RDONLY | OFLAGS)) == -1) + { + fprintf (stderr, "Unable to open() file '%s' (%s) !", dl_src, + strerror (errno)); + return FALSE; + } + + if ((tmp_dest = + open (dl_dest, O_WRONLY | O_CREAT | O_TRUNC | OFLAGS, permissions)) < 0) + { + close (tmp_src); + fprintf (stderr, "Unable to create file '%s' (%s) !", dl_src, + strerror (errno)); + return FALSE; + } + + for (;;) + { + return_tmp = read (tmp_src, dl_tmp, sizeof (dl_tmp)); + + if (!return_tmp) + { + ok = TRUE; + break; + } + + if (return_tmp < 0) + { + fprintf (stderr, "download_move_to_completed_dir(): " + "error reading while moving file to save directory (%s)", + strerror (errno)); + break; + } + + return_tmp2 = write (tmp_dest, dl_tmp, return_tmp); + + if (return_tmp2 < 0) + { + fprintf (stderr, "download_move_to_completed_dir(): " + "error writing while moving file to save directory (%s)", + strerror (errno)); + break; + } + + if (return_tmp < sizeof (dl_tmp)) + { + ok = TRUE; + break; + } + } + + close (tmp_dest); + close (tmp_src); + return ok; +} + +/* Takes care of moving a file from a temporary download location to a completed location. Now in UTF-8. */ +void +move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions) +{ + char src[4096]; + char dst[4096]; + int res, i; + char *src_fs; /* FileSystem encoding */ + char *dst_fs; + + /* if dcc_dir and dcc_completed_dir are the same then we are done */ + if (0 == strcmp (src_dir, dst_dir) || + 0 == dst_dir[0]) + return; /* Already in "completed dir" */ + + snprintf (src, sizeof (src), "%s/%s", src_dir, fname); + snprintf (dst, sizeof (dst), "%s/%s", dst_dir, fname); + + /* already exists in completed dir? Append a number */ + if (file_exists_utf8 (dst)) + { + for (i = 0; ; i++) + { + snprintf (dst, sizeof (dst), "%s/%s.%d", dst_dir, fname, i); + if (!file_exists_utf8 (dst)) + break; + } + } + + /* convert UTF-8 to filesystem encoding */ + src_fs = xchat_filename_from_utf8 (src, -1, 0, 0, 0); + if (!src_fs) + return; + dst_fs = xchat_filename_from_utf8 (dst, -1, 0, 0, 0); + if (!dst_fs) + { + g_free (src_fs); + return; + } + + /* first try a simple rename move */ + res = rename (src_fs, dst_fs); + + if (res == -1 && (errno == EXDEV || errno == EPERM)) + { + /* link failed because either the two paths aren't on the */ + /* same filesystem or the filesystem doesn't support hard */ + /* links, so we have to do a copy. */ + if (copy_file (src_fs, dst_fs, dccpermissions)) + unlink (src_fs); + } + + g_free (dst_fs); + g_free (src_fs); +} + +int +mkdir_utf8 (char *dir) +{ + int ret; + + dir = xchat_filename_from_utf8 (dir, -1, 0, 0, 0); + if (!dir) + return -1; + +#ifdef WIN32 + ret = mkdir (dir); +#else + ret = mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR); +#endif + g_free (dir); + + return ret; +} + +/* separates a string according to a 'sep' char, then calls the callback + function for each token found */ + +int +token_foreach (char *str, char sep, + int (*callback) (char *str, void *ud), void *ud) +{ + char t, *start = str; + + while (1) + { + if (*str == sep || *str == 0) + { + t = *str; + *str = 0; + if (callback (start, ud) < 1) + { + *str = t; + return FALSE; + } + *str = t; + + if (*str == 0) + break; + str++; + start = str; + + } else + { + /* chars $00-$7f can never be embedded in utf-8 */ + str++; + } + } + + return TRUE; +} + +/* 31 bit string hash functions */ + +guint32 +str_hash (const char *key) +{ + const char *p = key; + guint32 h = *p; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + + return h; +} + +guint32 +str_ihash (const unsigned char *key) +{ + const char *p = key; + guint32 h = rfc_tolowertab [(guint)*p]; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + rfc_tolowertab [(guint)*p]; + + return h; +} + +/* features: 1. "src" must be valid, NULL terminated UTF-8 */ +/* 2. "dest" will be left with valid UTF-8 - no partial chars! */ + +void +safe_strcpy (char *dest, const char *src, int bytes_left) +{ + int mbl; + + while (1) + { + mbl = g_utf8_skip[*((unsigned char *)src)]; + + if (bytes_left < (mbl + 1)) /* can't fit with NULL? */ + { + *dest = 0; + break; + } + + if (mbl == 1) /* one byte char */ + { + *dest = *src; + if (*src == 0) + break; /* it all fit */ + dest++; + src++; + bytes_left--; + } + else /* multibyte char */ + { + memcpy (dest, src, mbl); + dest += mbl; + src += mbl; + bytes_left -= mbl; + } + } +} diff --git a/src/common/util.h b/src/common/util.h new file mode 100644 index 00000000..fce45def --- /dev/null +++ b/src/common/util.h @@ -0,0 +1,54 @@ +/************************************************************************ + * This technique was borrowed in part from the source code to + * ircd-hybrid-5.3 to implement case-insensitive string matches which + * are fully compliant with Section 2.2 of RFC 1459, the copyright + * of that code being (C) 1990 Jarkko Oikarinen and under the GPL. + * + * A special thanks goes to Mr. Okarinen for being the one person who + * seems to have ever noticed this section in the original RFC and + * written code for it. Shame on all the rest of you (myself included). + * + * --+ Dagmar d'Surreal + */ + +#ifndef XCHAT_UTIL_H +#define XCHAT_UTIL_H + +#define rfc_tolower(c) (rfc_tolowertab[(unsigned char)(c)]) + +extern const unsigned char rfc_tolowertab[]; + +int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr); +char *expand_homedir (char *file); +void path_part (char *file, char *path, int pathlen); +int match (const char *mask, const char *string); +char *file_part (char *file); +void for_files (char *dirname, char *mask, void callback (char *file)); +int rfc_casecmp (const char *, const char *); +int rfc_ncasecmp (char *, char *, int); +int buf_get_line (char *, char **, int *, int len); +char *nocasestrstr (const char *text, const char *tofind); +char *country (char *); +void country_search (char *pattern, void *ud, void (*print)(void *, char *, ...)); +char *get_cpu_str (void); +int util_exec (const char *cmd); +int util_execv (char * const argv[]); +#define STRIP_COLOR 1 +#define STRIP_ATTRIB 2 +#define STRIP_HIDDEN 4 +#define STRIP_ESCMARKUP 8 +#define STRIP_ALL 7 +gchar *strip_color (const char *text, int len, int flags); +int strip_color2 (const char *src, int len, char *dst, int flags); +int strip_hidden_attribute (char *src, char *dst); +char *errorstring (int err); +int waitline (int sok, char *buf, int bufsize, int); +unsigned long make_ping_time (void); +void move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions); +int mkdir_utf8 (char *dir); +int token_foreach (char *str, char sep, int (*callback) (char *str, void *ud), void *ud); +guint32 str_hash (const char *key); +guint32 str_ihash (const unsigned char *key); +void safe_strcpy (char *dest, const char *src, int bytes_left); + +#endif diff --git a/src/common/xchat-plugin.h b/src/common/xchat-plugin.h new file mode 100644 index 00000000..30b19295 --- /dev/null +++ b/src/common/xchat-plugin.h @@ -0,0 +1,334 @@ +/* You can distribute this header with your plugins for easy compilation */ +#ifndef XCHAT_PLUGIN_H +#define XCHAT_PLUGIN_H + +#include <time.h> + +#define XCHAT_IFACE_MAJOR 1 +#define XCHAT_IFACE_MINOR 9 +#define XCHAT_IFACE_MICRO 11 +#define XCHAT_IFACE_VERSION ((XCHAT_IFACE_MAJOR * 10000) + \ + (XCHAT_IFACE_MINOR * 100) + \ + (XCHAT_IFACE_MICRO)) + +#define XCHAT_PRI_HIGHEST 127 +#define XCHAT_PRI_HIGH 64 +#define XCHAT_PRI_NORM 0 +#define XCHAT_PRI_LOW (-64) +#define XCHAT_PRI_LOWEST (-128) + +#define XCHAT_FD_READ 1 +#define XCHAT_FD_WRITE 2 +#define XCHAT_FD_EXCEPTION 4 +#define XCHAT_FD_NOTSOCKET 8 + +#define XCHAT_EAT_NONE 0 /* pass it on through! */ +#define XCHAT_EAT_XCHAT 1 /* don't let xchat see this event */ +#define XCHAT_EAT_PLUGIN 2 /* don't let other plugins see this event */ +#define XCHAT_EAT_ALL (XCHAT_EAT_XCHAT|XCHAT_EAT_PLUGIN) /* don't let anything see this event */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _xchat_plugin xchat_plugin; +typedef struct _xchat_list xchat_list; +typedef struct _xchat_hook xchat_hook; +#ifndef PLUGIN_C +typedef struct _xchat_context xchat_context; +#endif + +#ifndef PLUGIN_C +struct _xchat_plugin +{ + /* these are only used on win32 */ + xchat_hook *(*xchat_hook_command) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + const char *help_text, + void *userdata); + xchat_hook *(*xchat_hook_server) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_print) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph, + int timeout, + int (*callback) (void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph, + int fd, + int flags, + int (*callback) (int fd, int flags, void *user_data), + void *userdata); + void *(*xchat_unhook) (xchat_plugin *ph, + xchat_hook *hook); + void (*xchat_print) (xchat_plugin *ph, + const char *text); + void (*xchat_printf) (xchat_plugin *ph, + const char *format, ...); + void (*xchat_command) (xchat_plugin *ph, + const char *command); + void (*xchat_commandf) (xchat_plugin *ph, + const char *format, ...); + int (*xchat_nickcmp) (xchat_plugin *ph, + const char *s1, + const char *s2); + int (*xchat_set_context) (xchat_plugin *ph, + xchat_context *ctx); + xchat_context *(*xchat_find_context) (xchat_plugin *ph, + const char *servname, + const char *channel); + xchat_context *(*xchat_get_context) (xchat_plugin *ph); + const char *(*xchat_get_info) (xchat_plugin *ph, + const char *id); + int (*xchat_get_prefs) (xchat_plugin *ph, + const char *name, + const char **string, + int *integer); + xchat_list * (*xchat_list_get) (xchat_plugin *ph, + const char *name); + void (*xchat_list_free) (xchat_plugin *ph, + xchat_list *xlist); + const char * const * (*xchat_list_fields) (xchat_plugin *ph, + const char *name); + int (*xchat_list_next) (xchat_plugin *ph, + xchat_list *xlist); + const char * (*xchat_list_str) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + int (*xchat_list_int) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + void * (*xchat_plugingui_add) (xchat_plugin *ph, + const char *filename, + const char *name, + const char *desc, + const char *version, + char *reserved); + void (*xchat_plugingui_remove) (xchat_plugin *ph, + void *handle); + int (*xchat_emit_print) (xchat_plugin *ph, + const char *event_name, ...); + int (*xchat_read_fd) (xchat_plugin *ph, + void *src, + char *buf, + int *len); + time_t (*xchat_list_time) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + char *(*xchat_gettext) (xchat_plugin *ph, + const char *msgid); + void (*xchat_send_modes) (xchat_plugin *ph, + const char **targets, + int ntargets, + int modes_per_line, + char sign, + char mode); + char *(*xchat_strip) (xchat_plugin *ph, + const char *str, + int len, + int flags); + void (*xchat_free) (xchat_plugin *ph, + void *ptr); +}; +#endif + + +xchat_hook * +xchat_hook_command (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + const char *help_text, + void *userdata); + +xchat_hook * +xchat_hook_server (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + void *userdata); + +xchat_hook * +xchat_hook_print (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], void *user_data), + void *userdata); + +xchat_hook * +xchat_hook_timer (xchat_plugin *ph, + int timeout, + int (*callback) (void *user_data), + void *userdata); + +xchat_hook * +xchat_hook_fd (xchat_plugin *ph, + int fd, + int flags, + int (*callback) (int fd, int flags, void *user_data), + void *userdata); + +void * +xchat_unhook (xchat_plugin *ph, + xchat_hook *hook); + +void +xchat_print (xchat_plugin *ph, + const char *text); + +void +xchat_printf (xchat_plugin *ph, + const char *format, ...); + +void +xchat_command (xchat_plugin *ph, + const char *command); + +void +xchat_commandf (xchat_plugin *ph, + const char *format, ...); + +int +xchat_nickcmp (xchat_plugin *ph, + const char *s1, + const char *s2); + +int +xchat_set_context (xchat_plugin *ph, + xchat_context *ctx); + +xchat_context * +xchat_find_context (xchat_plugin *ph, + const char *servname, + const char *channel); + +xchat_context * +xchat_get_context (xchat_plugin *ph); + +const char * +xchat_get_info (xchat_plugin *ph, + const char *id); + +int +xchat_get_prefs (xchat_plugin *ph, + const char *name, + const char **string, + int *integer); + +xchat_list * +xchat_list_get (xchat_plugin *ph, + const char *name); + +void +xchat_list_free (xchat_plugin *ph, + xchat_list *xlist); + +const char * const * +xchat_list_fields (xchat_plugin *ph, + const char *name); + +int +xchat_list_next (xchat_plugin *ph, + xchat_list *xlist); + +const char * +xchat_list_str (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + +int +xchat_list_int (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + +time_t +xchat_list_time (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + +void * +xchat_plugingui_add (xchat_plugin *ph, + const char *filename, + const char *name, + const char *desc, + const char *version, + char *reserved); + +void +xchat_plugingui_remove (xchat_plugin *ph, + void *handle); + +int +xchat_emit_print (xchat_plugin *ph, + const char *event_name, ...); + +char * +xchat_gettext (xchat_plugin *ph, + const char *msgid); + +void +xchat_send_modes (xchat_plugin *ph, + const char **targets, + int ntargets, + int modes_per_line, + char sign, + char mode); + +char * +xchat_strip (xchat_plugin *ph, + const char *str, + int len, + int flags); + +void +xchat_free (xchat_plugin *ph, + void *ptr); + +#if !defined(PLUGIN_C) && defined(WIN32) +#ifndef XCHAT_PLUGIN_HANDLE +#define XCHAT_PLUGIN_HANDLE (ph) +#endif +#define xchat_hook_command ((XCHAT_PLUGIN_HANDLE)->xchat_hook_command) +#define xchat_hook_server ((XCHAT_PLUGIN_HANDLE)->xchat_hook_server) +#define xchat_hook_print ((XCHAT_PLUGIN_HANDLE)->xchat_hook_print) +#define xchat_hook_timer ((XCHAT_PLUGIN_HANDLE)->xchat_hook_timer) +#define xchat_hook_fd ((XCHAT_PLUGIN_HANDLE)->xchat_hook_fd) +#define xchat_unhook ((XCHAT_PLUGIN_HANDLE)->xchat_unhook) +#define xchat_print ((XCHAT_PLUGIN_HANDLE)->xchat_print) +#define xchat_printf ((XCHAT_PLUGIN_HANDLE)->xchat_printf) +#define xchat_command ((XCHAT_PLUGIN_HANDLE)->xchat_command) +#define xchat_commandf ((XCHAT_PLUGIN_HANDLE)->xchat_commandf) +#define xchat_nickcmp ((XCHAT_PLUGIN_HANDLE)->xchat_nickcmp) +#define xchat_set_context ((XCHAT_PLUGIN_HANDLE)->xchat_set_context) +#define xchat_find_context ((XCHAT_PLUGIN_HANDLE)->xchat_find_context) +#define xchat_get_context ((XCHAT_PLUGIN_HANDLE)->xchat_get_context) +#define xchat_get_info ((XCHAT_PLUGIN_HANDLE)->xchat_get_info) +#define xchat_get_prefs ((XCHAT_PLUGIN_HANDLE)->xchat_get_prefs) +#define xchat_list_get ((XCHAT_PLUGIN_HANDLE)->xchat_list_get) +#define xchat_list_free ((XCHAT_PLUGIN_HANDLE)->xchat_list_free) +#define xchat_list_fields ((XCHAT_PLUGIN_HANDLE)->xchat_list_fields) +#define xchat_list_str ((XCHAT_PLUGIN_HANDLE)->xchat_list_str) +#define xchat_list_int ((XCHAT_PLUGIN_HANDLE)->xchat_list_int) +#define xchat_list_time ((XCHAT_PLUGIN_HANDLE)->xchat_list_time) +#define xchat_list_next ((XCHAT_PLUGIN_HANDLE)->xchat_list_next) +#define xchat_plugingui_add ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_add) +#define xchat_plugingui_remove ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_remove) +#define xchat_emit_print ((XCHAT_PLUGIN_HANDLE)->xchat_emit_print) +#define xchat_gettext ((XCHAT_PLUGIN_HANDLE)->xchat_gettext) +#define xchat_send_modes ((XCHAT_PLUGIN_HANDLE)->xchat_send_modes) +#define xchat_strip ((XCHAT_PLUGIN_HANDLE)->xchat_strip) +#define xchat_free ((XCHAT_PLUGIN_HANDLE)->xchat_free) +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/common/xchat.c b/src/common/xchat.c new file mode 100644 index 00000000..afac9a0e --- /dev/null +++ b/src/common/xchat.c @@ -0,0 +1,951 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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 <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#define WANTSOCKET +#include "inet.h" + +#ifndef WIN32 +#include <sys/wait.h> +#include <signal.h> +#endif + +#include "xchat.h" +#include "fe.h" +#include "util.h" +#include "cfgfiles.h" +#include "chanopt.h" +#include "ignore.h" +#include "xchat-plugin.h" +#include "plugin.h" +#include "plugin-timer.h" +#include "notify.h" +#include "server.h" +#include "servlist.h" +#include "outbound.h" +#include "text.h" +#include "url.h" +#include "xchatc.h" + +#ifdef USE_OPENSSL +#include <openssl/ssl.h> /* SSL_() */ +#include "ssl.h" +#endif + +#ifdef USE_MSPROXY +#include "msproxy.h" +#endif + +#ifdef USE_LIBPROXY +#include <proxy.h> +#endif + +GSList *popup_list = 0; +GSList *button_list = 0; +GSList *dlgbutton_list = 0; +GSList *command_list = 0; +GSList *ctcp_list = 0; +GSList *replace_list = 0; +GSList *sess_list = 0; +GSList *dcc_list = 0; +GSList *ignore_list = 0; +GSList *usermenu_list = 0; +GSList *urlhandler_list = 0; +GSList *tabmenu_list = 0; + +static int in_xchat_exit = FALSE; +int xchat_is_quitting = FALSE; +/* command-line args */ +int arg_dont_autoconnect = FALSE; +int arg_skip_plugins = FALSE; +char *arg_url = NULL; +char *arg_command = NULL; +gint arg_existing = FALSE; + +#ifdef USE_DBUS +#include "dbus/dbus-client.h" +#include "dbus/dbus-plugin.h" +#endif /* USE_DBUS */ + +struct session *current_tab; +struct session *current_sess = 0; +struct xchatprefs prefs; + +#ifdef USE_OPENSSL +SSL_CTX *ctx = NULL; +#endif + +#ifdef USE_LIBPROXY +pxProxyFactory *libproxy_factory; +#endif + +int +is_session (session * sess) +{ + return g_slist_find (sess_list, sess) ? 1 : 0; +} + +session * +find_dialog (server *serv, char *nick) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv && sess->type == SESS_DIALOG) + { + if (!serv->p_cmp (nick, sess->channel)) + return (sess); + } + list = list->next; + } + return 0; +} + +session * +find_channel (server *serv, char *chan) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = list->data; + if ((!serv || serv == sess->server) && sess->type != SESS_DIALOG) + { + if (!serv->p_cmp (chan, sess->channel)) + return sess; + } + list = list->next; + } + return 0; +} + +static void +lagcheck_update (void) +{ + server *serv; + GSList *list = serv_list; + + if (!prefs.lagometer) + return; + + while (list) + { + serv = list->data; + if (serv->lag_sent) + fe_set_lag (serv, -1); + + list = list->next; + } +} + +void +lag_check (void) +{ + server *serv; + GSList *list = serv_list; + unsigned long tim; + char tbuf[128]; + time_t now = time (0); + int lag; + + tim = make_ping_time (); + + while (list) + { + serv = list->data; + if (serv->connected && serv->end_of_motd) + { + lag = now - serv->ping_recv; + if (prefs.pingtimeout && lag > prefs.pingtimeout && lag > 0) + { + sprintf (tbuf, "%d", lag); + EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL, + NULL, NULL, 0); + if (prefs.autoreconnect) + serv->auto_reconnect (serv, FALSE, -1); + } else + { + snprintf (tbuf, sizeof (tbuf), "LAG%lu", tim); + serv->p_ping (serv, "", tbuf); + serv->lag_sent = tim; + fe_set_lag (serv, -1); + } + } + list = list->next; + } +} + +static int +away_check (void) +{ + session *sess; + GSList *list; + int full, sent, loop = 0; + + if (!prefs.away_track || prefs.away_size_max < 1) + return 1; + +doover: + /* request an update of AWAY status of 1 channel every 30 seconds */ + full = TRUE; + sent = 0; /* number of WHOs (users) requested */ + list = sess_list; + while (list) + { + sess = list->data; + + if (sess->server->connected && + sess->type == SESS_CHANNEL && + sess->channel[0] && + sess->total <= prefs.away_size_max) + { + if (!sess->done_away_check) + { + full = FALSE; + + /* if we're under 31 WHOs, send another channels worth */ + if (sent < 31 && !sess->doing_who) + { + sess->done_away_check = TRUE; + sess->doing_who = TRUE; + /* this'll send a WHO #channel */ + sess->server->p_away_status (sess->server, sess->channel); + sent += sess->total; + } + } + } + + list = list->next; + } + + /* done them all, reset done_away_check to FALSE and start over */ + if (full) + { + list = sess_list; + while (list) + { + sess = list->data; + sess->done_away_check = FALSE; + list = list->next; + } + loop++; + if (loop < 2) + goto doover; + } + + return 1; +} + +static int +xchat_misc_checks (void) /* this gets called every 1/2 second */ +{ + static int count = 0; +#ifdef USE_MSPROXY + static int count2 = 0; +#endif + + count++; + + lagcheck_update (); /* every 500ms */ + + if (count % 2) + dcc_check_timeouts (); /* every 1 second */ + + if (count >= 60) /* every 30 seconds */ + { + if (prefs.lagometer) + lag_check (); + count = 0; + } + +#ifdef USE_MSPROXY + count2++; + if (count2 >= 720) /* 720 every 6 minutes */ + { + msproxy_keepalive (); + count2 = 0; + } +#endif + + return 1; +} + +/* executed when the first irc window opens */ + +static void +irc_init (session *sess) +{ + static int done_init = FALSE; + char buf[512]; + + if (done_init) + return; + + done_init = TRUE; + + plugin_add (sess, NULL, NULL, timer_plugin_init, NULL, NULL, FALSE); + +#ifdef USE_PLUGIN + if (!arg_skip_plugins) + plugin_auto_load (sess); /* autoload ~/.xchat *.so */ +#endif + +#ifdef USE_DBUS + plugin_add (sess, NULL, NULL, dbus_plugin_init, NULL, NULL, FALSE); +#endif + + if (prefs.notify_timeout) + notify_tag = fe_timeout_add (prefs.notify_timeout * 1000, + notify_checklist, 0); + + fe_timeout_add (prefs.away_timeout * 1000, away_check, 0); + fe_timeout_add (500, xchat_misc_checks, 0); + + if (arg_url != NULL) + { + snprintf (buf, sizeof (buf), "server %s", arg_url); + handle_command (sess, buf, FALSE); + g_free (arg_url); /* from GOption */ + } + + if (arg_command != NULL) + { + g_free (arg_command); + } + + /* load -e ~/.xchat2/startup.txt */ + snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), "startup.txt"); + load_perform_file (sess, buf); +} + +static session * +session_new (server *serv, char *from, int type, int focus) +{ + session *sess; + + sess = malloc (sizeof (struct session)); + memset (sess, 0, sizeof (struct session)); + + sess->server = serv; + sess->logfd = -1; + sess->scrollfd = -1; + sess->type = type; + + sess->alert_beep = SET_DEFAULT; + sess->alert_taskbar = SET_DEFAULT; + sess->alert_tray = SET_DEFAULT; + + sess->text_hidejoinpart = SET_DEFAULT; + sess->text_logging = SET_DEFAULT; + sess->text_scrollback = SET_DEFAULT; + + if (from != NULL) + safe_strcpy (sess->channel, from, CHANLEN); + + sess_list = g_slist_prepend (sess_list, sess); + + fe_new_window (sess, focus); + + return sess; +} + +session * +new_ircwindow (server *serv, char *name, int type, int focus) +{ + session *sess; + + switch (type) + { + case SESS_SERVER: + serv = server_new (); + if (prefs.use_server_tab) + sess = session_new (serv, name, SESS_SERVER, focus); + else + sess = session_new (serv, name, SESS_CHANNEL, focus); + serv->server_session = sess; + serv->front_session = sess; + break; + case SESS_DIALOG: + sess = session_new (serv, name, type, focus); + log_open_or_close (sess); + break; + default: +/* case SESS_CHANNEL: + case SESS_NOTICES: + case SESS_SNOTICES:*/ + sess = session_new (serv, name, type, focus); + break; + } + + irc_init (sess); + scrollback_load (sess); + chanopt_load (sess); + plugin_emit_dummy_print (sess, "Open Context"); + + return sess; +} + +static void +exec_notify_kill (session * sess) +{ +#ifndef WIN32 + struct nbexec *re; + if (sess->running_exec != NULL) + { + re = sess->running_exec; + sess->running_exec = NULL; + kill (re->childpid, SIGKILL); + waitpid (re->childpid, NULL, WNOHANG); + fe_input_remove (re->iotag); + close (re->myfd); + if (re->linebuf) + free(re->linebuf); + free (re); + } +#endif +} + +static void +send_quit_or_part (session * killsess) +{ + int willquit = TRUE; + GSList *list; + session *sess; + server *killserv = killsess->server; + + /* check if this is the last session using this server */ + list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == killserv && sess != killsess) + { + willquit = FALSE; + list = 0; + } else + list = list->next; + } + + if (xchat_is_quitting) + willquit = TRUE; + + if (killserv->connected) + { + if (willquit) + { + if (!killserv->sent_quit) + { + killserv->flush_queue (killserv); + server_sendquit (killsess); + killserv->sent_quit = TRUE; + } + } else + { + if (killsess->type == SESS_CHANNEL && killsess->channel[0] && + !killserv->sent_quit) + { + server_sendpart (killserv, killsess->channel, 0); + } + } + } +} + +void +session_free (session *killsess) +{ + server *killserv = killsess->server; + session *sess; + GSList *list; + + plugin_emit_dummy_print (killsess, "Close Context"); + + if (current_tab == killsess) + current_tab = NULL; + + if (killserv->server_session == killsess) + killserv->server_session = NULL; + + if (killserv->front_session == killsess) + { + /* front_session is closed, find a valid replacement */ + killserv->front_session = NULL; + list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess != killsess && sess->server == killserv) + { + killserv->front_session = sess; + if (!killserv->server_session) + killserv->server_session = sess; + break; + } + list = list->next; + } + } + + if (!killserv->server_session) + killserv->server_session = killserv->front_session; + + sess_list = g_slist_remove (sess_list, killsess); + + if (killsess->type == SESS_CHANNEL) + userlist_free (killsess); + + exec_notify_kill (killsess); + + log_close (killsess); + scrollback_close (killsess); + chanopt_save (killsess); + + send_quit_or_part (killsess); + + history_free (&killsess->history); + if (killsess->topic) + free (killsess->topic); + if (killsess->current_modes) + free (killsess->current_modes); + + fe_session_callback (killsess); + + if (current_sess == killsess) + { + current_sess = NULL; + if (sess_list) + current_sess = sess_list->data; + } + + free (killsess); + + if (!sess_list && !in_xchat_exit) + xchat_exit (); /* sess_list is empty, quit! */ + + list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == killserv) + return; /* this server is still being used! */ + list = list->next; + } + + server_free (killserv); +} + +static void +free_sessions (void) +{ + GSList *list = sess_list; + + while (list) + { + fe_close_window (list->data); + list = sess_list; + } +} + + +static char defaultconf_ctcp[] = + "NAME TIME\n" "CMD nctcp %s TIME %t\n\n"\ + "NAME PING\n" "CMD nctcp %s PING %d\n\n"; + +static char defaultconf_replace[] = + "NAME teh\n" "CMD the\n\n"; +/* "NAME r\n" "CMD are\n\n"\ + "NAME u\n" "CMD you\n\n"*/ + +static char defaultconf_commands[] = + "NAME ACTION\n" "CMD me &2\n\n"\ + "NAME AME\n" "CMD allchan me &2\n\n"\ + "NAME ANICK\n" "CMD allserv nick &2\n\n"\ + "NAME AMSG\n" "CMD allchan say &2\n\n"\ + "NAME BANLIST\n" "CMD quote MODE %c +b\n\n"\ + "NAME CHAT\n" "CMD dcc chat %2\n\n"\ + "NAME DIALOG\n" "CMD query %2\n\n"\ + "NAME DMSG\n" "CMD msg =%2 &3\n\n"\ + "NAME EXIT\n" "CMD quit\n\n"\ + "NAME GREP\n" "CMD lastlog -r &2\n\n"\ + "NAME J\n" "CMD join &2\n\n"\ + "NAME KILL\n" "CMD quote KILL %2 :&3\n\n"\ + "NAME LEAVE\n" "CMD part &2\n\n"\ + "NAME M\n" "CMD msg &2\n\n"\ + "NAME ONOTICE\n" "CMD notice @%c &2\n\n"\ + "NAME RAW\n" "CMD quote &2\n\n"\ + "NAME SERVHELP\n" "CMD quote HELP\n\n"\ + "NAME SPING\n" "CMD ping\n\n"\ + "NAME SQUERY\n" "CMD quote SQUERY %2 :&3\n\n"\ + "NAME SSLSERVER\n" "CMD server -ssl &2\n\n"\ + "NAME SV\n" "CMD echo xchat %v %m\n\n"\ + "NAME UMODE\n" "CMD mode %n &2\n\n"\ + "NAME UPTIME\n" "CMD quote STATS u\n\n"\ + "NAME VER\n" "CMD ctcp %2 VERSION\n\n"\ + "NAME VERSION\n" "CMD ctcp %2 VERSION\n\n"\ + "NAME WALLOPS\n" "CMD quote WALLOPS :&2\n\n"\ + "NAME WII\n" "CMD quote WHOIS %2 %2\n\n"; + +static char defaultconf_urlhandlers[] = + "NAME Open Link in Opera\n" "CMD !opera -remote 'openURL(%s)'\n\n"; + +#ifdef USE_SIGACTION +/* Close and open log files on SIGUSR1. Usefull for log rotating */ + +static void +sigusr1_handler (int signal, siginfo_t *si, void *un) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + log_open_or_close (sess); + list = list->next; + } +} + +/* Execute /SIGUSR2 when SIGUSR2 received */ + +static void +sigusr2_handler (int signal, siginfo_t *si, void *un) +{ + session *sess = current_sess; + + if (sess) + handle_command (sess, "SIGUSR2", FALSE); +} +#endif + +static gint +xchat_auto_connect (gpointer userdata) +{ + servlist_auto_connect (NULL); + return 0; +} + +static void +xchat_init (void) +{ + char buf[3068]; + const char *cs = NULL; + +#ifdef WIN32 + WSADATA wsadata; + +#ifdef USE_IPV6 + if (WSAStartup(0x0202, &wsadata) != 0) + { + MessageBox (NULL, "Cannot find winsock 2.2+", "Error", MB_OK); + exit (0); + } +#else + WSAStartup(0x0101, &wsadata); +#endif /* !USE_IPV6 */ +#endif /* !WIN32 */ + +#ifdef USE_SIGACTION + struct sigaction act; + + /* ignore SIGPIPE's */ + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigaction (SIGPIPE, &act, NULL); + + /* Deal with SIGUSR1's & SIGUSR2's */ + act.sa_sigaction = sigusr1_handler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigaction (SIGUSR1, &act, NULL); + + act.sa_sigaction = sigusr2_handler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigaction (SIGUSR2, &act, NULL); +#else +#ifndef WIN32 + /* good enough for these old systems */ + signal (SIGPIPE, SIG_IGN); +#endif +#endif + + if (g_get_charset (&cs)) + prefs.utf8_locale = TRUE; + + load_text_events (); + sound_load (); + notify_load (); + ignore_load (); + + snprintf (buf, sizeof (buf), + "NAME %s~%s~\n" "CMD query %%s\n\n"\ + "NAME %s~%s~\n" "CMD send %%s\n\n"\ + "NAME %s~%s~\n" "CMD whois %%s %%s\n\n"\ + "NAME %s~%s~\n" "CMD notify -n ASK %%s\n\n"\ + + "NAME SUB\n" "CMD %s\n\n"\ + "NAME %s\n" "CMD op %%a\n\n"\ + "NAME %s\n" "CMD deop %%a\n\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME %s\n" "CMD voice %%a\n\n"\ + "NAME %s\n" "CMD devoice %%a\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME SUB\n" "CMD %s\n\n"\ + "NAME %s\n" "CMD kick %%s\n\n"\ + "NAME %s\n" "CMD ban %%s\n\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME %s *!*@*.host\n""CMD ban %%s 0\n\n"\ + "NAME %s *!*@domain\n""CMD ban %%s 1\n\n"\ + "NAME %s *!*user@*.host\n""CMD ban %%s 2\n\n"\ + "NAME %s *!*user@domain\n""CMD ban %%s 3\n\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME %s *!*@*.host\n""CMD kickban %%s 0\n\n"\ + "NAME %s *!*@domain\n""CMD kickban %%s 1\n\n"\ + "NAME %s *!*user@*.host\n""CMD kickban %%s 2\n\n"\ + "NAME %s *!*user@domain\n""CMD kickban %%s 3\n\n"\ + "NAME ENDSUB\n" "CMD \n\n"\ + "NAME ENDSUB\n" "CMD \n\n", + + _("_Open Dialog Window"), "xchat-dialog", + _("_Send a File"), "gtk-floppy", + _("_User Info (WhoIs)"), "gtk-info", + _("_Add to Friends List"), "gtk-add", + _("O_perator Actions"), + + _("Give Ops"), + _("Take Ops"), + _("Give Voice"), + _("Take Voice"), + + _("Kick/Ban"), + _("Kick"), + _("Ban"), + _("Ban"), + _("Ban"), + _("Ban"), + _("Ban"), + _("KickBan"), + _("KickBan"), + _("KickBan"), + _("KickBan")); + + list_loadconf ("popup.conf", &popup_list, buf); + + snprintf (buf, sizeof (buf), + "NAME %s\n" "CMD part\n\n" + "NAME %s\n" "CMD getstr # join \"%s\"\n\n" + "NAME %s\n" "CMD quote LINKS\n\n" + "NAME %s\n" "CMD ping\n\n" + "NAME TOGGLE %s\n" "CMD irc_hide_version\n\n", + _("Leave Channel"), + _("Join Channel..."), + _("Enter Channel to Join:"), + _("Server Links"), + _("Ping Server"), + _("Hide Version")); + list_loadconf ("usermenu.conf", &usermenu_list, buf); + + snprintf (buf, sizeof (buf), + "NAME %s\n" "CMD op %%a\n\n" + "NAME %s\n" "CMD deop %%a\n\n" + "NAME %s\n" "CMD ban %%s\n\n" + "NAME %s\n" "CMD getstr \"%s\" \"kick %%s\" \"%s\"\n\n" + "NAME %s\n" "CMD send %%s\n\n" + "NAME %s\n" "CMD query %%s\n\n", + _("Op"), + _("DeOp"), + _("Ban"), + _("Kick"), + _("bye"), + _("Enter reason to kick %s:"), + _("Sendfile"), + _("Dialog")); + list_loadconf ("buttons.conf", &button_list, buf); + + snprintf (buf, sizeof (buf), + "NAME %s\n" "CMD whois %%s %%s\n\n" + "NAME %s\n" "CMD send %%s\n\n" + "NAME %s\n" "CMD dcc chat %%s\n\n" + "NAME %s\n" "CMD clear\n\n" + "NAME %s\n" "CMD ping %%s\n\n", + _("WhoIs"), + _("Send"), + _("Chat"), + _("Clear"), + _("Ping")); + list_loadconf ("dlgbuttons.conf", &dlgbutton_list, buf); + + list_loadconf ("tabmenu.conf", &tabmenu_list, NULL); + list_loadconf ("ctcpreply.conf", &ctcp_list, defaultconf_ctcp); + list_loadconf ("commands.conf", &command_list, defaultconf_commands); + list_loadconf ("replace.conf", &replace_list, defaultconf_replace); + list_loadconf ("urlhandlers.conf", &urlhandler_list, + defaultconf_urlhandlers); + + servlist_init (); /* load server list */ + + /* if we got a URL, don't open the server list GUI */ + if (!prefs.slist_skip && !arg_url) + fe_serverlist_open (NULL); + + /* turned OFF via -a arg */ + if (!arg_dont_autoconnect) + { + /* do any auto connects */ + if (!servlist_have_auto ()) /* if no new windows open .. */ + { + /* and no serverlist gui ... */ + if (prefs.slist_skip || arg_url) + /* we'll have to open one. */ + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + } else + { + fe_idle_add (xchat_auto_connect, NULL); + } + } else + { + if (prefs.slist_skip || arg_url) + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + } +} + +void +xchat_exit (void) +{ + xchat_is_quitting = TRUE; + in_xchat_exit = TRUE; + plugin_kill_all (); + fe_cleanup (); + if (prefs.autosave) + { + save_config (); + if (prefs.save_pevents) + pevent_save (NULL); + } + if (prefs.autosave_url) + url_autosave (); + sound_save (); + notify_save (); + ignore_save (); + free_sessions (); + chanopt_save_all (); + servlist_cleanup (); + fe_exit (); +} + +#ifndef WIN32 + +static int +child_handler (gpointer userdata) +{ + int pid = GPOINTER_TO_INT (userdata); + + if (waitpid (pid, 0, WNOHANG) == pid) + return 0; /* remove timeout handler */ + return 1; /* keep the timeout handler */ +} + +#endif + +void +xchat_exec (const char *cmd) +{ +#ifdef WIN32 + util_exec (cmd); +#else + int pid = util_exec (cmd); + if (pid != -1) + /* zombie avoiding system. Don't ask! it has to be like this to work + with zvt (which overrides the default handler) */ + fe_timeout_add (1000, child_handler, GINT_TO_POINTER (pid)); +#endif +} + +void +xchat_execv (char * const argv[]) +{ +#ifdef WIN32 + util_execv (argv); +#else + int pid = util_execv (argv); + if (pid != -1) + /* zombie avoiding system. Don't ask! it has to be like this to work + with zvt (which overrides the default handler) */ + fe_timeout_add (1000, child_handler, GINT_TO_POINTER (pid)); +#endif +} + +int +main (int argc, char *argv[]) +{ + int ret; + + srand (time (0)); /* CL: do this only once! */ + +#ifdef SOCKS + SOCKSinit (argv[0]); +#endif + + ret = fe_args (argc, argv); + if (ret != -1) + return ret; + +#ifdef USE_DBUS + xchat_remote (); +#endif + + load_config (); + +#ifdef USE_LIBPROXY + libproxy_factory = px_proxy_factory_new(); +#endif + + fe_init (); + + xchat_init (); + + fe_main (); + +#ifdef USE_LIBPROXY + px_proxy_factory_free(libproxy_factory); +#endif + +#ifdef USE_OPENSSL + if (ctx) + _SSL_context_free (ctx); +#endif + +#ifdef USE_DEBUG + xchat_mem_list (); +#endif + +#ifdef WIN32 + WSACleanup (); +#endif + + return 0; +} diff --git a/src/common/xchat.h b/src/common/xchat.h new file mode 100644 index 00000000..db7a6c4b --- /dev/null +++ b/src/common/xchat.h @@ -0,0 +1,575 @@ +#include "../../config.h" + +#include <glib/gslist.h> +#include <glib/glist.h> +#include <glib/gutils.h> +#include <glib/giochannel.h> +#include <glib/gstrfuncs.h> +#include <time.h> /* need time_t */ + +#ifndef XCHAT_H +#define XCHAT_H + +#include "history.h" + +#ifndef HAVE_SNPRINTF +#define snprintf g_snprintf +#endif + +#ifndef HAVE_VSNPRINTF +#define vsnprintf g_vsnprintf +#endif + +#ifdef USE_DEBUG +#define malloc(n) xchat_malloc(n, __FILE__, __LINE__) +#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__) +#define free(n) xchat_dfree(n, __FILE__, __LINE__) +#define strdup(n) xchat_strdup(n, __FILE__, __LINE__) +void *xchat_malloc (int size, char *file, int line); +void *xchat_strdup (char *str, char *file, int line); +void xchat_dfree (void *buf, char *file, int line); +void *xchat_realloc (char *old, int len, char *file, int line); +#endif + +#ifdef SOCKS +#ifdef __sgi +#include <sys/time.h> +#define INCLUDE_PROTOTYPES 1 +#endif +#include <socks.h> +#endif + +#ifdef USE_OPENSSL +#include <openssl/ssl.h> /* SSL_() */ +#endif + +#ifdef __EMX__ /* for o/s 2 */ +#define OFLAGS O_BINARY +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define PATH_MAX MAXPATHLEN +#define FILEPATH_LEN_MAX MAXPATHLEN +#endif + +/* force a 32bit CMP.L */ +#define CMPL(a, c0, c1, c2, c3) (a == (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24))) +#define WORDL(c0, c1, c2, c3) (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)) +#define WORDW(c0, c1) (guint16)(c0 | (c1 << 8)) + +#ifdef WIN32 /* for win32 */ +#define OFLAGS O_BINARY +#define sleep(t) _sleep(t*1000) +#include <direct.h> +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 +#ifndef S_ISDIR +#define S_ISDIR(m) ((m) & _S_IFDIR) +#endif +#define NETWORK_PRIVATE +#else /* for unix */ +#define OFLAGS 0 +#endif + +#define FONTNAMELEN 127 +#define PATHLEN 255 +#define DOMAINLEN 100 +#define NICKLEN 64 /* including the NULL, so 63 really */ +#define CHANLEN 300 +#define PDIWORDS 32 +#define USERNAMELEN 10 +#define HIDDEN_CHAR 8 /* invisible character for xtext */ + +#if defined(ENABLE_NLS) && !defined(_) +# include <libintl.h> +# define _(x) gettext(x) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#endif +#if !defined(_) +# define N_(String) (String) +# define _(x) (x) +#endif + +struct nbexec +{ + int myfd; + int childpid; + int tochannel; /* making this int keeps the struct 4-byte aligned */ + int iotag; + char *linebuf; + int buffill; + struct session *sess; +}; + +struct xchatprefs +{ + char nick1[NICKLEN]; + char nick2[NICKLEN]; + char nick3[NICKLEN]; + char realname[127]; + char username[127]; + char nick_suffix[4]; /* Only ever holds a one-character string. */ + char awayreason[256]; + char quitreason[256]; + char partreason[256]; + char font_normal[FONTNAMELEN + 1]; + char doubleclickuser[256]; + char sounddir[PATHLEN + 1]; + char soundcmd[PATHLEN + 1]; + char background[PATHLEN + 1]; + char dccdir[PATHLEN + 1]; + char dcc_completed_dir[PATHLEN + 1]; + char irc_extra_hilight[300]; + char irc_no_hilight[300]; + char irc_nick_hilight[300]; + char dnsprogram[72]; + char hostname[127]; + char cmdchar[4]; + char logmask[256]; + char stamp_format[64]; + char timestamp_log_format[64]; + char irc_id_ytext[64]; + char irc_id_ntext[64]; + + char proxy_host[64]; + int proxy_port; + int proxy_type; /* 0=disabled, 1=wingate 2=socks4, 3=socks5, 4=http */ + int proxy_use; /* 0=all 1=IRC_ONLY 2=DCC_ONLY */ + unsigned int proxy_auth; + char proxy_user[32]; + char proxy_pass[32]; + + int first_dcc_send_port; + int last_dcc_send_port; + + int tint_red; + int tint_green; + int tint_blue; + + int away_timeout; + int away_size_max; + + int gui_pane_left_size; + int gui_pane_right_size; + + int gui_ulist_pos; + int tab_pos; + + int _tabs_position; + int tab_layout; + int max_auto_indent; + int dcc_blocksize; + int max_lines; + int notify_timeout; + int dcctimeout; + int dccstalltimeout; + int dcc_global_max_get_cps; + int dcc_global_max_send_cps; + int dcc_max_get_cps; + int dcc_max_send_cps; + int mainwindow_left; + int mainwindow_top; + int mainwindow_width; + int mainwindow_height; + int completion_sort; + int gui_win_state; + int gui_url_mod; + int gui_usermenu; + int gui_join_dialog; + int gui_quit_dialog; + int dialog_left; + int dialog_top; + int dialog_width; + int dialog_height; + int dccpermissions; + int recon_delay; + int bantype; + int userlist_sort; + guint32 local_ip; + guint32 dcc_ip; + char dcc_ip_str[DOMAINLEN + 1]; + + unsigned int tab_small; + unsigned int tab_sort; + unsigned int mainwindow_save; + unsigned int perc_color; + unsigned int perc_ascii; + unsigned int autosave; + unsigned int autodialog; + unsigned int autosave_url; + unsigned int autoreconnect; + unsigned int autoreconnectonfail; + unsigned int invisible; + unsigned int servernotice; + unsigned int wallops; + unsigned int skipmotd; + unsigned int autorejoin; + unsigned int colorednicks; + unsigned int chanmodebuttons; + unsigned int userlistbuttons; + unsigned int showhostname_in_userlist; + unsigned int nickcompletion; + unsigned int completion_amount; + unsigned int tabchannels; + unsigned int paned_userlist; + unsigned int autodccchat; + unsigned int autodccsend; + unsigned int autoresume; + unsigned int autoopendccsendwindow; + unsigned int autoopendccrecvwindow; + unsigned int autoopendccchatwindow; + unsigned int transparent; + unsigned int stripcolor; + unsigned int timestamp; + unsigned int fastdccsend; + unsigned int dcc_send_fillspaces; + unsigned int dcc_remove; + unsigned int slist_fav; + unsigned int slist_skip; + unsigned int slist_select; + unsigned int filterbeep; + + unsigned int input_balloon_chans; + unsigned int input_balloon_hilight; + unsigned int input_balloon_priv; + unsigned int input_balloon_time; + + unsigned int input_beep_chans; + unsigned int input_beep_hilight; + unsigned int input_beep_priv; + + unsigned int input_flash_chans; + unsigned int input_flash_hilight; + unsigned int input_flash_priv; + + unsigned int input_tray_chans; + unsigned int input_tray_hilight; + unsigned int input_tray_priv; + + unsigned int truncchans; + unsigned int privmsgtab; + unsigned int irc_join_delay; + unsigned int logging; + unsigned int timestamp_logs; + unsigned int newtabstofront; + unsigned int dccwithnick; + unsigned int hidever; + unsigned int ip_from_server; + unsigned int raw_modes; + unsigned int show_away_once; + unsigned int show_away_message; + unsigned int auto_unmark_away; + unsigned int away_track; + unsigned int userhost; + unsigned int irc_whois_front; + unsigned int use_server_tab; + unsigned int notices_tabs; + unsigned int style_namelistgad; + unsigned int style_inputbox; + unsigned int windows_as_tabs; + unsigned int indent_nicks; + unsigned int text_replay; + unsigned int show_marker; + unsigned int show_separator; + unsigned int thin_separator; + unsigned int auto_indent; + unsigned int wordwrap; + unsigned int gui_input_spell; + unsigned int gui_tray; + unsigned int gui_tray_flags; + unsigned int gui_tweaks; + unsigned int _gui_ulist_left; + unsigned int throttle; + unsigned int topicbar; + unsigned int hideuserlist; + unsigned int hidemenu; + unsigned int perlwarnings; + unsigned int lagometer; + unsigned int throttlemeter; + unsigned int pingtimeout; + unsigned int whois_on_notifyonline; + unsigned int wait_on_exit; + unsigned int confmode; + unsigned int utf8_locale; + unsigned int identd; + + unsigned int ctcp_number_limit; /*flood */ + unsigned int ctcp_time_limit; /*seconds of floods */ + + unsigned int msg_number_limit; /*same deal */ + unsigned int msg_time_limit; + + /* Tells us if we need to save, only when they've been edited. + This is so that we continue using internal defaults (which can + change in the next release) until the user edits them. */ + unsigned int save_pevents:1; +}; + +/* Session types */ +#define SESS_SERVER 1 +#define SESS_CHANNEL 2 +#define SESS_DIALOG 3 +#define SESS_NOTICES 4 +#define SESS_SNOTICES 5 + +/* Per-Channel Settings */ +#define SET_OFF 0 +#define SET_ON 1 +#define SET_DEFAULT 2 /* use global setting */ + +typedef struct session +{ + /* Per-Channel Alerts */ + /* use a byte, because we need a pointer to each element */ + guint8 alert_beep; + guint8 alert_taskbar; + guint8 alert_tray; + + /* Per-Channel Settings */ + guint8 text_hidejoinpart; + guint8 text_logging; + guint8 text_scrollback; + + struct server *server; + void *usertree_alpha; /* pure alphabetical tree */ + void *usertree; /* ordered with Ops first */ + struct User *me; /* points to myself in the usertree */ + char channel[CHANLEN]; + char waitchannel[CHANLEN]; /* waiting to join channel (/join sent) */ + char willjoinchannel[CHANLEN]; /* will issue /join for this channel */ + char channelkey[64]; /* XXX correct max length? */ + int limit; /* channel user limit */ + int logfd; + int scrollfd; /* scrollback filedes */ + int scrollwritten; /* number of lines written */ + + char lastnick[NICKLEN]; /* last nick you /msg'ed */ + + struct history history; + + int ops; /* num. of ops in channel */ + int hops; /* num. of half-oped users */ + int voices; /* num. of voiced people */ + int total; /* num. of users in channel */ + + char *quitreason; + char *topic; + char *current_modes; /* free() me */ + + int mode_timeout_tag; + + struct session *lastlog_sess; + struct nbexec *running_exec; + + struct session_gui *gui; /* initialized by fe_new_window */ + struct restore_gui *res; + + int type; /* SESS_* */ + + int new_data:1; /* new data avail? (purple tab) */ + int nick_said:1; /* your nick mentioned? (blue tab) */ + int msg_said:1; /* new msg available? (red tab) */ + + int ignore_date:1; + int ignore_mode:1; + int ignore_names:1; + int end_of_names:1; + int doing_who:1; /* /who sent on this channel */ + int done_away_check:1; /* done checking for away status changes */ + unsigned int lastlog_regexp:1; /* this is a lastlog and using regexp */ +} session; + +struct msproxy_state_t +{ + gint32 clientid; + gint32 serverid; + unsigned char seq_recv; /* seq number of last packet recv. */ + unsigned char seq_sent; /* seq number of last packet sent. */ +}; + +typedef struct server +{ + /* server control operations (in server*.c) */ + void (*connect)(struct server *, char *hostname, int port, int no_login); + void (*disconnect)(struct session *, int sendquit, int err); + int (*cleanup)(struct server *); + void (*flush_queue)(struct server *); + void (*auto_reconnect)(struct server *, int send_quit, int err); + /* irc protocol functions (in proto*.c) */ + void (*p_inline)(struct server *, char *buf, int len); + void (*p_invite)(struct server *, char *channel, char *nick); + void (*p_cycle)(struct server *, char *channel, char *key); + void (*p_ctcp)(struct server *, char *to, char *msg); + void (*p_nctcp)(struct server *, char *to, char *msg); + void (*p_quit)(struct server *, char *reason); + void (*p_kick)(struct server *, char *channel, char *nick, char *reason); + void (*p_part)(struct server *, char *channel, char *reason); + void (*p_ns_identify)(struct server *, char *pass); + void (*p_ns_ghost)(struct server *, char *usname, char *pass); + void (*p_join)(struct server *, char *channel, char *key); + void (*p_join_list)(struct server *, GSList *channels, GSList *keys); + void (*p_login)(struct server *, char *user, char *realname); + void (*p_join_info)(struct server *, char *channel); + void (*p_mode)(struct server *, char *target, char *mode); + void (*p_user_list)(struct server *, char *channel); + void (*p_away_status)(struct server *, char *channel); + void (*p_whois)(struct server *, char *nicks); + void (*p_get_ip)(struct server *, char *nick); + void (*p_get_ip_uh)(struct server *, char *nick); + void (*p_set_back)(struct server *); + void (*p_set_away)(struct server *, char *reason); + void (*p_message)(struct server *, char *channel, char *text); + void (*p_action)(struct server *, char *channel, char *act); + void (*p_notice)(struct server *, char *channel, char *text); + void (*p_topic)(struct server *, char *channel, char *topic); + void (*p_list_channels)(struct server *, char *arg, int min_users); + void (*p_change_nick)(struct server *, char *new_nick); + void (*p_names)(struct server *, char *channel); + void (*p_ping)(struct server *, char *to, char *timestring); +/* void (*p_set_away)(struct server *);*/ + int (*p_raw)(struct server *, char *raw); + int (*p_cmp)(const char *s1, const char *s2); + + int port; + int sok; /* is equal to sok4 or sok6 (the one we are using) */ + int sok4; /* tcp4 socket */ + int sok6; /* tcp6 socket */ + int proxy_type; + int proxy_sok; /* Additional information for MS Proxy beast */ + int proxy_sok4; + int proxy_sok6; + struct msproxy_state_t msp_state; + int id; /* unique ID number (for plugin API) */ +#ifdef USE_OPENSSL + SSL *ssl; + int ssl_do_connect_tag; +#else + void *ssl; +#endif + int childread; + int childwrite; + int childpid; + int iotag; + int recondelay_tag; /* reconnect delay timeout */ + int joindelay_tag; /* waiting before we send JOIN */ + char hostname[128]; /* real ip number */ + char servername[128]; /* what the server says is its name */ + char password[86]; + char nick[NICKLEN]; + char linebuf[2048]; /* RFC says 512 chars including \r\n */ + char *last_away_reason; + int pos; /* current position in linebuf */ + int nickcount; + int nickservtype; /* 0=/MSG nickserv 1=/NICKSERV 2=/NS */ + + char *chantypes; /* for 005 numeric - free me */ + char *chanmodes; /* for 005 numeric - free me */ + char *nick_prefixes; /* e.g. "*@%+" */ + char *nick_modes; /* e.g. "aohv" */ + char *bad_nick_prefixes; /* for ircd that doesn't give the modes */ + int modes_per_line; /* 6 on undernet, 4 on efnet etc... */ + + void *network; /* points to entry in servlist.c or NULL! */ + + GSList *outbound_queue; + time_t next_send; /* cptr->since in ircu */ + time_t prev_now; /* previous now-time */ + int sendq_len; /* queue size */ + int lag; /* milliseconds */ + + struct session *front_session; /* front-most window/tab */ + struct session *server_session; /* server window/tab */ + + struct server_gui *gui; /* initialized by fe_new_server */ + + unsigned int ctcp_counter; /*flood */ + time_t ctcp_last_time; + + unsigned int msg_counter; /*counts the msg tab opened in a certain time */ + time_t msg_last_time; + + /*time_t connect_time;*/ /* when did it connect? */ + time_t lag_sent; + time_t ping_recv; /* when we last got a ping reply */ + time_t away_time; /* when we were marked away */ + + char *encoding; /* NULL for system */ + char *autojoin; /* list of channels & keys to join */ + + unsigned int motd_skipped:1; + unsigned int connected:1; + unsigned int connecting:1; + unsigned int no_login:1; + unsigned int skip_next_userhost:1;/* used for "get my ip from server" */ + unsigned int skip_next_whois:1; /* hide whois output */ + unsigned int inside_whois:1; + unsigned int doing_dns:1; /* /dns has been done */ + unsigned int end_of_motd:1; /* end of motd reached (logged in) */ + unsigned int sent_quit:1; /* sent a QUIT already? */ + unsigned int use_listargs:1; /* undernet and dalnet need /list >0,<10000 */ + unsigned int is_away:1; + unsigned int reconnect_away:1; /* whether to reconnect in is_away state */ + unsigned int dont_use_proxy:1; /* to proxy or not to proxy */ + unsigned int supports_watch:1; /* supports the WATCH command */ + unsigned int bad_prefix:1; /* gave us a bad PREFIX= 005 number */ + unsigned int have_namesx:1; /* 005 tokens NAMESX and UHNAMES */ + unsigned int have_uhnames:1; + unsigned int have_whox:1; /* have undernet's WHOX features */ + unsigned int have_capab:1; /* supports CAPAB (005 tells us) */ + unsigned int have_idmsg:1; /* freenode's IDENTIFY-MSG */ + unsigned int have_except:1; /* ban exemptions +e */ + unsigned int using_cp1255:1; /* encoding is CP1255/WINDOWS-1255? */ + unsigned int using_irc:1; /* encoding is "IRC" (CP1252/UTF-8 hybrid)? */ + unsigned int use_who:1; /* whether to use WHO command to get dcc_ip */ +#ifdef USE_OPENSSL + unsigned int use_ssl:1; /* is server SSL capable? */ + unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */ +#endif +} server; + +typedef int (*cmd_callback) (struct session * sess, char *tbuf, char *word[], + char *word_eol[]); + +struct commands +{ + char *name; + cmd_callback callback; + char needserver; + char needchannel; + gint16 handle_quotes; + char *help; +}; + +struct away_msg +{ + struct server *server; + char nick[NICKLEN]; + char *message; +}; + +/* not just for popups, but used for usercommands, ctcp replies, + userlist buttons etc */ + +struct popup +{ + char *cmd; + char *name; +}; + +/* CL: get a random int in the range [0..n-1]. DON'T use rand() % n, it gives terrible results. */ +#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n))) + +#if defined(WIN32) && GLIB_CHECK_VERSION(2,4,0) +#define xchat_filename_from_utf8 g_locale_from_utf8 +#define xchat_filename_to_utf8 g_locale_to_utf8 +#else +#define xchat_filename_from_utf8 g_filename_from_utf8 +#define xchat_filename_to_utf8 g_filename_to_utf8 +#endif + +#endif diff --git a/src/common/xchatc.h b/src/common/xchatc.h new file mode 100644 index 00000000..c22e17dd --- /dev/null +++ b/src/common/xchatc.h @@ -0,0 +1,39 @@ +#ifndef XCHAT_C_H +#define XCHAT_C_H + +extern struct xchatprefs prefs; + +extern int xchat_is_quitting; +extern gint arg_skip_plugins; /* command-line args */ +extern gint arg_dont_autoconnect; +extern char *arg_url; +extern char *arg_command; +extern gint arg_existing; + +extern session *current_sess; +extern session *current_tab; + +extern GSList *popup_list; +extern GSList *button_list; +extern GSList *dlgbutton_list; +extern GSList *command_list; +extern GSList *ctcp_list; +extern GSList *replace_list; +extern GSList *sess_list; +extern GSList *dcc_list; +extern GSList *ignore_list; +extern GSList *usermenu_list; +extern GSList *urlhandler_list; +extern GSList *tabmenu_list; + +session * find_channel (server *serv, char *chan); +session * find_dialog (server *serv, char *nick); +session * new_ircwindow (server *serv, char *name, int type, int focus); +int is_session (session * sess); +void session_free (session *killsess); +void lag_check (void); +void xchat_exit (void); +void xchat_exec (const char *cmd); +void xchat_execv (char * const argv[]); + +#endif |