/* silc-server.c : irssi Copyright (C) 2000 - 2007 Timo Sirainen Pekka Riikonen 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 "module.h" #include "net-nonblock.h" #include "net-sendbuffer.h" #include "signals.h" #include "servers.h" #include "commands.h" #include "levels.h" #include "modules.h" #include "rawlog.h" #include "misc.h" #include "settings.h" #include "servers-setup.h" #include "channels-setup.h" #include "client_ops.h" #include "silc-servers.h" #include "silc-channels.h" #include "silc-queries.h" #include "silc-nicklist.h" #include "silc-cmdqueue.h" #include "window-item-def.h" #include "fe-common/core/printtext.h" #include "fe-common/core/fe-channels.h" #include "fe-common/core/keyboard.h" #include "fe-common/silc/module-formats.h" #include "silc-commands.h" void silc_servers_reconnect_init(void); void silc_servers_reconnect_deinit(void); int silc_send_channel(SILC_SERVER_REC *server, char *channel, char *msg, SilcMessageFlags flags) { SILC_CHANNEL_REC *rec; rec = silc_channel_find(server, channel); if (rec == NULL || rec->entry == NULL) { cmd_return_error_value(CMDERR_NOT_JOINED, FALSE); } return silc_client_send_channel_message(silc_client, server->conn, rec->entry, NULL, flags, sha1hash, msg, strlen(msg)); } typedef struct { char *nick; char *msg; int len; SilcMessageFlags flags; SILC_SERVER_REC *server; } PRIVMSG_REC; /* Callback function that sends the private message if the client was resolved from the server. */ static void silc_send_msg_clients(SilcClient client, SilcClientConnection conn, SilcStatus status, SilcDList clients, void *context) { PRIVMSG_REC *rec = context; SILC_SERVER_REC *server = rec->server; SilcClientEntry target; if (!clients) { printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s: There is no such client", rec->nick); goto out; } /* Find the correct one. The rec->nick might be a formatted nick so this will find the correct one. */ target = silc_dlist_get(clients); clients = silc_client_get_clients_local(silc_client, server->conn, rec->nick, FALSE); if (!clients) { if (strchr(rec->nick, '@') && target->server) printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s: There is no such client (did you mean %s@%s?)", rec->nick, target->nickname, target->server); else printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s: There is no such client (did you mean %s?)", rec->nick, target->nickname); goto out; } /* Send the private message */ silc_dlist_start(clients); target = silc_dlist_get(clients); silc_client_send_private_message(client, conn, target, rec->flags, sha1hash, rec->msg, rec->len); out: silc_client_list_free(silc_client, server->conn, clients); g_free(rec->nick); g_free(rec->msg); g_free(rec); } int silc_send_msg(SILC_SERVER_REC *server, char *nick, char *msg, int msg_len, SilcMessageFlags flags) { PRIVMSG_REC *rec; SilcDList clients; SilcClientEntry target; int ret; /* Find client entry */ clients = silc_client_get_clients_local(silc_client, server->conn, nick, FALSE); if (!clients) { char *nickname = NULL; rec = g_new0(PRIVMSG_REC, 1); rec->nick = g_strdup(nick); rec->msg = g_strdup(msg); rec->server = server; rec->flags = flags; rec->len = msg_len; silc_client_nickname_parse(silc_client, server->conn, nick, &nickname); if (!nickname) nickname = strdup(nick); /* Could not find client with that nick, resolve it from server. */ silc_client_get_clients_whois(silc_client, server->conn, nickname, NULL, NULL, silc_send_msg_clients, rec); silc_free(nickname); return TRUE; } /* Send the private message directly */ target = silc_dlist_get(clients); ret = silc_client_send_private_message(silc_client, server->conn, target, flags, sha1hash, msg, msg_len); silc_client_list_free(silc_client, server->conn, clients); return ret; } void silc_send_mime(SILC_SERVER_REC *server, int channel, const char *to, const char *data, int sign) { char *unescaped_data; SilcUInt32 unescaped_data_len; int target_type; if (!(IS_SILC_SERVER(server)) || (data == NULL) || (to == NULL)) return; if (channel) { target_type = SEND_TARGET_CHANNEL; } else { target_type = server_ischannel(SERVER(server), to) ? SEND_TARGET_CHANNEL : SEND_TARGET_NICK; } unescaped_data = silc_unescape_data(data, &unescaped_data_len); if (target_type == SEND_TARGET_CHANNEL) { SILC_CHANNEL_REC *rec; rec = silc_channel_find(server, to); if (rec == NULL || rec->entry == NULL) { cmd_return_error(CMDERR_NOT_JOINED); } silc_client_send_channel_message(silc_client, server->conn, rec->entry, NULL, SILC_MESSAGE_FLAG_DATA | (sign ? SILC_MESSAGE_FLAG_SIGNED : 0), sha1hash, unescaped_data, unescaped_data_len); } else { silc_send_msg(server, (char *)to, unescaped_data, unescaped_data_len, SILC_MESSAGE_FLAG_DATA | (sign ? SILC_MESSAGE_FLAG_SIGNED : 0)); } signal_stop(); silc_free(unescaped_data); } static int isnickflag_func(SERVER_REC *server, char flag) { return flag == '@' || flag == '+'; } static int ischannel_func(SERVER_REC *server, const char *data) { return FALSE; } const char *get_nick_flags(SERVER_REC *server) { return "@\0\0"; } static void send_message(SILC_SERVER_REC *server, char *target, char *msg, int target_type) { char *message = NULL, *t = NULL; int len; SilcBool sign; g_return_if_fail(server != NULL); g_return_if_fail(target != NULL); g_return_if_fail(msg != NULL); if (!silc_term_utf8()) { len = silc_utf8_encoded_len(msg, strlen(msg), SILC_STRING_LOCALE); message = silc_calloc(len + 1, sizeof(*message)); g_return_if_fail(message != NULL); silc_utf8_encode(msg, strlen(msg), SILC_STRING_LOCALE, message, len); } if (target_type == SEND_TARGET_CHANNEL) { sign = settings_get_bool("sign_channel_messages"); silc_send_channel(server, target, message ? message : msg, SILC_MESSAGE_FLAG_UTF8 | (sign ? SILC_MESSAGE_FLAG_SIGNED : 0)); } else { sign = settings_get_bool("sign_private_messages"); if (!silc_term_utf8()) { len = silc_utf8_encoded_len(target, strlen(target), SILC_STRING_LOCALE); t = silc_calloc(len + 1, sizeof(*t)); g_return_if_fail(t != NULL); silc_utf8_encode(target, strlen(target), SILC_STRING_LOCALE, t, len); } silc_send_msg(server, t ? t : target, message ? message : msg, message ? strlen(message) : strlen(msg), SILC_MESSAGE_FLAG_UTF8 | (sign ? SILC_MESSAGE_FLAG_SIGNED : 0)); } silc_free(message); silc_free(t); } /* Connection callback */ static void silc_connect_cb(SilcClient client, SilcClientConnection conn, SilcClientConnectionStatus status, SilcStatus error, const char *message, void *context) { SILC_SERVER_REC *server = context; FtpSession ftp; char *file; SILC_LOG_DEBUG(("Connection callback %p, status %d, error %d, message %s", conn, status, error, message ? message : "N/A")); server->op = NULL; switch (status) { case SILC_CLIENT_CONN_SUCCESS: if (server->disconnected) { silc_client_close_connection(client, conn); return; } /* We have successfully connected to server */ /* Enable queueing until we have our requested nick */ if (((opt_nickname && !silc_utf8_strcasecmp(opt_nickname, conn->local_entry->nickname)) || (settings_get_str("nick") && !silc_utf8_strcasecmp(settings_get_str("nick"), conn->local_entry->nickname))) && silc_utf8_strcasecmp(conn->local_entry->nickname, conn->local_entry->username)) silc_queue_enable(conn); /* Put default attributes */ silc_query_attributes_default(silc_client, conn); server->connected = TRUE; server->conn = conn; server->conn->context = server; signal_emit("event connected", 1, server); break; case SILC_CLIENT_CONN_SUCCESS_RESUME: if (server->disconnected) { silc_client_close_connection(client, conn); return; } /* We have successfully resumed old detached session */ server->connected = TRUE; server->conn = conn; server->conn->context = server; signal_emit("event connected", 1, server); /* Put default attributes */ silc_query_attributes_default(silc_client, conn); /* Remove the detach data now */ file = silc_get_session_filename(server); unlink(file); silc_free(file); break; case SILC_CLIENT_CONN_DISCONNECTED: /* Server disconnected */ if (server->conn && server->conn->local_entry) { nicklist_rename_unique(SERVER(server), server->conn->local_entry, server->nick, server->conn->local_entry, silc_client->username); silc_change_nick(server, silc_client->username); } if (message) silc_say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, "Server closed connection: %s (%d) %s", silc_get_status_message(error), error, message ? message : ""); /* Close FTP sessions */ silc_dlist_start(server->ftp_sessions); while ((ftp = silc_dlist_get(server->ftp_sessions))) silc_client_file_close(client, conn, ftp->session_id); silc_dlist_uninit(server->ftp_sessions); if (server->conn) server->conn->context = NULL; server->conn = NULL; server->connection_lost = TRUE; if (!server->disconnected) server_disconnect(SERVER(server)); server_unref(SERVER(server)); break; default: file = silc_get_session_filename(server); if (silc_file_size(file) > 0) printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, SILCTXT_REATTACH_FAILED, file); silc_free(file); server->connection_lost = TRUE; server->conn = NULL; if (server->conn) server->conn->context = NULL; if (!server->disconnected) server_disconnect(SERVER(server)); server_unref(SERVER(server)); break; } } /* Called after TCP stream has been created */ static void sig_connected_stream_created(SilcSocketStreamStatus status, SilcStream stream, void *context) { SILC_SERVER_REC *server = context; SilcClientConnectionParams params; char *file; server->tcp_op = NULL; if (!stream) { server->connection_lost = TRUE; server_disconnect(SERVER(server)); return; } if (server->disconnected) { silc_stream_destroy(stream); return; } /* Set connection parameters */ memset(¶ms, 0, sizeof(params)); params.nickname = (opt_nickname ? (char *)opt_nickname : (char *)settings_get_str("nick")); params.timeout_secs = settings_get_int("key_exchange_timeout_secs"); params.rekey_secs = settings_get_int("key_exchange_rekey_secs"); params.pfs = settings_get_bool("key_exchange_rekey_pfs"); /* Try to read detached session data and use it if found. */ file = silc_get_session_filename(server); params.detach_data = silc_file_readfile(file, ¶ms.detach_data_len, NULL); if (params.detach_data) params.detach_data[params.detach_data_len] = 0; if (params.detach_data) printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, SILCTXT_REATTACH, server->tag); silc_free(file); /* Start key exchange */ server->op = silc_client_key_exchange(silc_client, ¶ms, irssi_pubkey, irssi_privkey, stream, SILC_CONN_SERVER, silc_connect_cb, server); if (!server->op) { server->connection_lost = TRUE; server_disconnect(SERVER(server)); silc_stream_destroy(stream); return; } server_ref(SERVER(server)); server->ftp_sessions = silc_dlist_init(); server->isnickflag = isnickflag_func; server->ischannel = ischannel_func; server->get_nick_flags = get_nick_flags; server->send_message = (void *) send_message; } static void sig_connected(SILC_SERVER_REC *server) { int fd; if (!IS_SILC_SERVER(server)) return; /* Wrap the socket to TCP stream */ fd = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle)); server->tcp_op = silc_socket_tcp_stream_create(fd, TRUE, FALSE, silc_client->schedule, sig_connected_stream_created, server); } static void sig_disconnected(SILC_SERVER_REC *server) { if (!IS_SILC_SERVER(server)) return; if (server->conn) { /* Close connection */ silc_client_close_connection(silc_client, server->conn); } else if (server->op) { /* Abort on going connecting (key exchange) */ silc_async_abort(server->op, NULL, NULL); server->op = NULL; } else if (server->tcp_op) { /* Abort on going TCP stream creation */ silc_async_abort(server->tcp_op, NULL, NULL); server->tcp_op = NULL; } /* SILC closes the handle */ if (server->handle) { g_io_channel_unref(net_sendbuffer_handle(server->handle)); net_sendbuffer_destroy(server->handle, FALSE); server->handle = NULL; } } SERVER_REC *silc_server_init_connect(SERVER_CONNECT_REC *conn) { SILC_SERVER_REC *server; g_return_val_if_fail(IS_SILC_SERVER_CONNECT(conn), NULL); if (conn->address == NULL || *conn->address == '\0') return NULL; if (conn->nick == NULL || *conn->nick == '\0') { silc_say_error("Cannot connect: nickname is not set"); return NULL; } server = g_new0(SILC_SERVER_REC, 1); server->chat_type = SILC_PROTOCOL; server->connrec = (SILC_SERVER_CONNECT_REC *)conn; server_connect_ref(conn); if (server->connrec->port <= 0) server->connrec->port = 706; server_connect_init((SERVER_REC *)server); return (SERVER_REC *)server; } void silc_server_connect(SERVER_REC *server) { if (!server_start_connect(server)) { server_connect_unref(server->connrec); g_free(server); return; } } /* Return a string of all channels in server in server->channels_join() format */ char *silc_server_get_channels(SILC_SERVER_REC *server) { GSList *tmp; GString *chans; char *ret; g_return_val_if_fail(server != NULL, FALSE); chans = g_string_new(NULL); for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { CHANNEL_REC *channel = tmp->data; CHANNEL_SETUP_REC *schannel; if ((schannel = channel_setup_find(channel->name, server->connrec->chatnet)) && schannel->password) g_string_sprintfa(chans, "%s %s,", channel->name, schannel->password); else g_string_sprintfa(chans, "%s,", channel->name); } if (chans->len > 0) g_string_truncate(chans, chans->len-1); ret = chans->str; g_string_free(chans, FALSE); return ret; } /* Syntaxes of all SILC commands for HELP files (the help file generation will snoop these from here). */ /* SYNTAX: BAN [+|-[[@[![@hostname>]]]]] */ /* SYNTAX: CMODE +|- [{ }] */ /* SYNTAX: CUMODE +|- [@] */ /* SYNTAX: GETKEY */ /* SYNTAX: INVITE [[@hostname>] */ /* SYNTAX: INVITE [+|-[[@[![@hostname>]]]]] */ /* SYNTAX: KEY MSG set|unset|list|agreement|negotiate [] */ /* SYNTAX: KEY CHANNEL set|unset|list|change [] */ /* SYNTAX: KICK [@] [] */ /* SYNTAX: KILL [@] [] [-pubkey] */ /* SYNTAX: OPER [-pubkey] */ /* SYNTAX: SILCOPER [-pubkey] */ /* SYNTAX: TOPIC [] */ /* SYNTAX: UMODE +|- */ /* SYNTAX: WHOIS [[@]] [-details] [-pubkey ] [] */ /* SYNTAX: WHOWAS [@] [] */ /* SYNTAX: CLOSE [] */ /* SYNTAX: MOTD [] */ /* SYNTAX: LIST [] */ /* SYNTAX: ME */ /* SYNTAX: ACTION [-sign] [-channel] */ /* SYNTAX: AWAY [] */ /* SYNTAX: INFO [] */ /* SYNTAX: NICK */ /* SYNTAX: NOTICE [-sign] [-channel] */ /* SYNTAX: PART [] */ /* SYNTAX: PING */ /* SYNTAX: USERS */ /* SYNTAX: FILE SEND [ []] [-no-listener]*/ /* SYNTAX: FILE ACCEPT [] */ /* SYNTAX: FILE CLOSE [] */ /* SYNTAX: FILE */ /* SYNTAX: JOIN [] [-cipher ] [-hmac ] [-founder] [-auth [ []]]*/ /* SYNTAX: DETACH */ /* SYNTAX: WATCH [<-add | -del> ] [-pubkey +|-] */ /* SYNTAX: STATS */ /* SYNTAX: ATTR [<-del>