/* Silky - A GTK+ client for SILC. Copyright (C) 2003, 2004, 2005 Toni Willberg - Callbacks for SILC protocol events 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 http://silky.sourceforge.net/ */ #include #include #include #include #include #ifdef HAVE_CONFIG_H # include #endif #include "files.h" #include "support.h" #include "keys.h" #include "log.h" #include "gui.h" #include "silc-command-reply.h" #include "silc-notify.h" #include "ui_buddylist.h" #include "ui_console.h" #include "ui_channel.h" #include "ui_query.h" #include "common.h" extern GladeXML *xmlmain; extern silkyStruct *silky; /* the client */ extern gchar *previous_nick; /* my nickname just before sending NICK command */ extern gchar *glade_silky_channel; extern gchar *glade_silky_channel_label; extern gchar *silkydir; /* Directory for $HOME/.silky */ SilcClientOperations ops; /* callbacks */ extern SilkyChannel *channels; /* from channel.c */ extern SilkyChannel *channel_active; extern gint lastping; /* last time we sent a ping */ extern gint lastpong; /* last time we received a ping */ /* "say" client operation is a message from client library to application. It may include error messages or something else. */ void silc_say(SilcClient client, SilcClientConnection conn, SilcClientMessageType type, char *msg, ...) { /* DISABLED FOR NOW, what to do with this? char str[200]; va_list va; va_start(va, msg); g_vsnprintf(str, sizeof(str)-1, msg, va); printconsole(g_strdup_printf("%s", str)); debug("message type for say: '%d'", type); va_end(va); */ } /* Message for a channel. The 'sender' is the sender of the message and 'channel' is naturally the channel the message was said on. */ void silc_channel_message(SilcClient client, SilcClientConnection conn, SilcClientEntry sender, SilcChannelEntry channel, SilcMessagePayload payload, SilcChannelPrivateKey key, SilcMessageFlags flags, const unsigned char *message, SilcUInt32 message_len) { gchar content_type[128]; gchar transfer_encoding[128]; unsigned char *mime_data_buffer; SilcUInt32 mime_data_len; bool valid_mime; unsigned int header_length; bool error, sgn = 0; // char *error_message = ""; debug("silc_channel_message()"); memset(content_type, 0, sizeof(content_type)); memset(transfer_encoding, 0, sizeof(transfer_encoding)); valid_mime = silc_mime_parse(message, message_len, NULL, 0, content_type, sizeof(content_type) - 1, transfer_encoding, sizeof(transfer_encoding) - 1, &mime_data_buffer, &mime_data_len); if( flags & SILC_MESSAGE_FLAG_SIGNED ) { SilcMessageSignedPayload sig = silc_message_get_signature(payload); sgn = verify_message_signature(sender, sig, payload); } if (valid_mime == TRUE) { debug("MIME: A valid MIME message received, "); debug("MIME: type: '%s', transfer encoding: '%s' size: '%d'", content_type, transfer_encoding, (unsigned int) mime_data_len); header_length = message_len - mime_data_len; debug("MIME: message_len: '%d'", (unsigned int) message_len); debug("MIME: mime_data_len: '%d'", (unsigned int) mime_data_len); debug("MIME: header_length: '%d'", (unsigned int) header_length); error = TRUE; /* KNOWN TYPES */ if ( !strcmp(content_type, "image/gif") || !strcmp(content_type, "image/png") || !strcmp(content_type, "image/jpeg") || !strcmp(content_type, "text/plain") ) { print_mime_to_channel(sender, channel->channel_name, 0, g_strdup(content_type), mime_data_buffer, mime_data_len, sgn); error = FALSE; } /* UNKNOWN TYPE */ if (error) { debug("MIME: unhandled type, giving error"); /* can not handle this type */ printchannel (channel->channel_name, g_strdup_printf(_("%s sent a message of unknown type '%s'"), sender->nickname, content_type) ); } } else { debug("NOT MIME, a plain text message"); /* PLAIN TEXT or INVALID MIME */ print_mime_to_channel(sender, channel->channel_name, sgn, g_strdup("text/plain"), (int)flags, message); } debug("silc_channel_message() returning "); } /* Private messages. */ void silc_private_message(SilcClient client, SilcClientConnection conn, SilcClientEntry sender, SilcMessagePayload payload, SilcMessageFlags flags, const unsigned char *message, SilcUInt32 message_len) { debug("silc_private_message()"); /* print the message */ printquery(sender->id, sender->nickname, flags, message); } /* Command handler. This function is called every time in the command function. Likewise, this will be called if an error occurs. 'conn' is the associated client connection. 'cmd_context' is the command context originally sent to the command. 'success' is a boolean value; FALSE if an error occurred during command. 'command' is the command currently being processed. It must be noted that this is not a reply from server. This is just a function that gets called after the application calls the command. In effect, this is done just to tell the application that a command really was processed. */ void silc_command(SilcClient client, SilcClientConnection conn, SilcClientCommandContext cmd_context, bool success, SilcCommand command, SilcStatus status) { debug("COMMAND PROCESSED: '%s' (%s)", silc_get_command_name(command), silc_get_status_message(status)); if (success) { debug(" command was successful"); } else { debug(" command was not successful"); printconsole( g_strdup_printf(_("Command %s failed: %s"), silc_get_command_name(command), silc_get_status_message(status))); } } /* Called to indicate whether connection was successfully established or not. This is also the very first time the application receives SilcClientConnection object which it should save someplace. If 'success' if FALSE, the application MUST call the function silc_client_close_connection. */ void silc_connected(SilcClient client, SilcClientConnection conn, SilcClientConnectionStatus status) { GtkLabel *label; GtkWindow *window; gchar *title; GtkWidget *menuwidget; /* set struct */ silkyStruct *silky = client->application; debug("silc_connected(), status[%d]", status); /* success */ switch (status) { case SILC_CLIENT_CONN_SUCCESS: debug("SILC_CLIENT_CONN_SUCCESS, setting state to STATE_CONNECTED"); printconsole(g_strdup_printf(_("Connected to %s, please wait..."), conn->remote_host) ); break; case SILC_CLIENT_CONN_SUCCESS_RESUME: debug("SILC_CLIENT_CONN_SUCCESS_RESUME, setting state to STATE_CONNECTED"); printconsole(g_strdup_printf(_("Resumed connection to %s, please wait..."), conn->remote_host) ); /* FIXME: does this work just like this? we don't yet have detach part... */ break; /* no success */ case SILC_CLIENT_CONN_ERROR: debug("SILC_CLIENT_CONN_ERROR, Could not connect to server, setting STATE_DISCONNECTED!"); printconsole(_("There was an unknown error while connecting to the server.")); silky->state = STATE_DISCONNECTED; /* we need to close the connection now */ silc_client_close_connection(client, conn); /* enable connect from the menu */ menuwidget = glade_xml_get_widget (xmlmain, "menu_server_connect"); gtk_widget_set_sensitive(menuwidget, TRUE); return; break; case SILC_CLIENT_CONN_ERROR_TIMEOUT: debug("SILC_CLIENT_CONN_ERROR_TIMEOUT, Could not connect to server, setting STATE_DISCONNECTED!"); printconsole(_("Connection to server timed out")); silky->state = STATE_DISCONNECTED; /* we need to close the connection now */ silc_client_close_connection(client, conn); /* enable connect from the menu */ menuwidget = glade_xml_get_widget (xmlmain, "menu_server_connect"); gtk_widget_set_sensitive(menuwidget, TRUE); return; break; default: debug("unknown status code '%d', bugger, atleast we are not connected...", status); silky->state = STATE_DISCONNECTED; printconsole(_("There was an error while connecting to the server.")); /* enable connect from the menu */ menuwidget = glade_xml_get_widget (xmlmain, "menu_server_connect"); gtk_widget_set_sensitive(menuwidget, TRUE); return; break; } /* it was successful if we are here.. */ /* set state and connection */ silky->state = STATE_CONNECTED; silky->conn = conn; /* init buddylist WATCH commands, buddylist.c */ buddy_init_watchlist(); /* enable disconnect from the menu */ menuwidget = glade_xml_get_widget (xmlmain, "menu_server_disconnect"); gtk_widget_set_sensitive(menuwidget, TRUE); /* Update the GUI's server topic */ label = GTK_LABEL(glade_xml_get_widget (xmlmain, "servertopic")); gtk_label_set_text(label, conn->remote_host); /* update window title */ title = g_strdup_printf("Silky v%s : %s", SILKY_VERSION, conn->remote_host); window = GTK_WINDOW(glade_xml_get_widget (xmlmain, "window_main")); gtk_window_set_title(window, title); g_free(title); } /* Indicated that connection to a server was disconnected. The 'status' may tell the reason; if 'message' is non-NULL it may even include the disconnection message received from server. Clears nicklists of all channel tabs. */ void silc_disconnected(SilcClient client, SilcClientConnection conn, SilcStatus status, const char *msg) { gchar *title; GtkWindow *window; GtkWidget *menuwidget; silkyStruct *silky = client->application; SilkyChannel *ch; GtkWidget *statusbar; guint context; debug("setting state to STATE_DISCONNECTED"); silky->state = STATE_DISCONNECTED; /* now let's enable Connect and disable Disconnect from the menu... */ menuwidget = glade_xml_get_widget (xmlmain, "menu_server_disconnect"); gtk_widget_set_sensitive(menuwidget, FALSE); menuwidget = glade_xml_get_widget (xmlmain, "menu_server_connect"); gtk_widget_set_sensitive(menuwidget, TRUE); /* clear status bar, it could have lag notice at this point */ statusbar = glade_xml_get_widget (xmlmain, "silkystatusbar"); context = gtk_statusbar_get_context_id(GTK_STATUSBAR(glade_xml_get_widget (xmlmain, "silkystatusbar")), "Lag"); gtk_statusbar_push(GTK_STATUSBAR(statusbar), context, ""); printconsole(g_strdup_printf(_("Disconnected from %s. (%s)"), conn->remote_host, (msg ? msg : "") ) ); /* update menu items */ gtk_widget_set_sensitive(glade_xml_get_widget (xmlmain, "menu_server_connect"), TRUE); gtk_widget_set_sensitive(glade_xml_get_widget (xmlmain, "menu_server_disconnect"), FALSE); /* update window title */ title = g_strdup_printf("Silky v%s", SILKY_VERSION); window = GTK_WINDOW(glade_xml_get_widget (xmlmain, "window_main")); gtk_window_set_title(window, title); g_free(title); /* FIXME: there should be a function for this */ for( ch = channels; ch; ch = ch->next ) { /* set nicklist insensitive */ GtkTreeView *treeview = GTK_TREE_VIEW(glade_xml_get_widget(ch->glade_xml, "nicktreeview")); gtk_widget_set_sensitive( GTK_WIDGET(treeview), FALSE ); } /* free all channel lists */ channel_free_all(); /* update the buddy list, it sets unknown icons to buddies when we go offline */ refresh_gui_buddylist(); /* Server disconnected us, nullify the pointer. */ silky->conn = NULL; debug("returning"); } /* FIXME: what's this? */ typedef struct { SilcGetAuthMeth completion; void *context; } *InternalGetAuthMethod; /* Callback called when we've received the authentication method information from the server after we've requested it. This will get the authentication data from the user if needed. */ void silc_get_auth_method_callback(SilcClient client, SilcClientConnection conn, SilcAuthMethod auth_meth, void *context) { InternalGetAuthMethod internal = (InternalGetAuthMethod)context; debug("silc_get_auth_method_callback"); switch (auth_meth) { case SILC_AUTH_NONE: /* No authentication required. */ (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); break; case SILC_AUTH_PUBLIC_KEY: /* Do not get the authentication data now, the library will generate it using our default key, if we do not provide it here. */ /* XXX In the future when we support multiple local keys and multiple local certificates we will need to ask from user which one to use. */ (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); break; } silc_free(internal); } /* Find authentication method and auth data by hostname and port info. Hostname may be IP-address as well. */ void silc_get_auth_method(SilcClient client, SilcClientConnection conn, char *hostname, SilcUInt16 port, SilcGetAuthMeth completion, void *context) { InternalGetAuthMethod internal; debug("silc_get_auth_method()"); /* If we do not have this connection configured by the user in a configuration file then resolve the authentication method from the server for this session. */ internal = silc_calloc(1, sizeof(*internal)); internal->completion = completion; internal->context = context; silc_client_request_authentication_method(client, conn, silc_get_auth_method_callback, internal); } /* Verify a received public key. The 'conn_type' indicates which entity (server, client, whatnot) has sent the public key. The user or application may save the key as trusted public key for later use. 'completion' must be called after the public key has been verified. */ void silc_verify_public_key(SilcClient client, SilcClientConnection conn, SilcSocketType conn_type, unsigned char *pk, SilcUInt32 pk_len, SilcSKEPKType pk_type, SilcVerifyPublicKey completion, void *ctx) { SilcPublicKey pkey; gboolean result; bool ret; debug("silc_verify_public_key()"); ret = silc_pkcs_public_key_decode(pk, pk_len, &pkey); if (ret != TRUE) { /* That was not a SILC key! */ debug("could not decode received SILC public key"); printconsole(_("Invalid key.")); completion(FALSE, ctx); return; } /* If the key already exists, skip saving it entirely */ if (conn_type == SILC_SOCKET_TYPE_SERVER || conn_type == SILC_SOCKET_TYPE_ROUTER) { result = silky_known_server_key(pkey); } else { result = silky_known_client_key(pkey); } if (result) { debug("received SILC public key was already stored"); completion(TRUE, ctx); return; } /* Save the key */ result = silky_save_public_key(client, conn, NULL, conn_type, pk, pk_len, pk_type, completion, ctx); if (result == TRUE) { debug("user accepted the key"); completion(TRUE, ctx); } else { /* ask to stop connecting */ debug("user rejected the key"); printconsole(_("Rejected the key.")); completion(FALSE, ctx); } debug("returning"); return; } /* Ask a passphrase from user. The passphrase is returned to the library by calling completion() callback with 'context'. The returned passphrase should be in UTF-8 format, if not then the library will attempt to encode. */ void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, SilcAskPassphrase completion, void *context) { debug("silc_ask_passphrase()"); /* fixme: nothing should call this function, can it be removed? */ completion(NULL, 0, context); } /* Notifies application about a failure packet. This function get called if there is some protocol active in the client. 'protocol' is the protocol context. 'failure' is opaque pointer to the failure indicator. Note: 'failure' is protocol dependant and application must explicitly cast it to correct type. Usually it is a 32-bit failure type. */ void silc_failure(SilcClient client, SilcClientConnection conn, SilcProtocol proto, void *failure) { /* Something weird happened. 'failure' would hold the actual info, but that's not an issue yet. */ debug("silc_failure()"); } /* This function is executed every time the user (application) is asked to whether we would like to perform key agreement protocol. Returns TRUE is the user wants the library to perform key agreement, FALSE if not. In case of TRUE, both 'completion' and 'context' must be set. */ bool silc_key_agreement(SilcClient client, SilcClientConnection conn, SilcClientEntry entry, const char *hostname, SilcUInt16 port, SilcKeyAgreementCallback *completion, void **context) { debug("silc_key_agreement()"); return FALSE; } /* File transfer session request. silky does not support this. */ void silc_ftp(SilcClient client, SilcClientConnection conn, SilcClientEntry entry, SilcUInt32 session_id, const char *hostname, SilcUInt16 port) { debug("silc_ftp()"); } /* SILC session detachment data, delivered to the application. Not implemented currently. */ void silc_detach(SilcClient client, SilcClientConnection conn, const unsigned char *detach_data, SilcUInt32 data_len) { debug("silc_detach()"); } /* Client operations. This structure is filled with functions and given as an argument to the silc_client_alloc function. SILC Client Library requires that all of these functions are specified. */ SilcClientOperations ops = { silc_say, silc_channel_message, silc_private_message, silc_notify, silc_command, silc_command_reply, silc_connected, silc_disconnected, silc_get_auth_method, silc_verify_public_key, silc_ask_passphrase, silc_failure, silc_key_agreement, silc_ftp, silc_detach };