From: Pekka Riikonen Date: Wed, 1 Nov 2000 21:44:27 +0000 (+0000) Subject: Moved silc_client_ch[u]mode[_char] to client library from silc/. X-Git-Tag: SILC.0.1~336 X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=commitdiff_plain;h=bb1973faaa81ead23b3d5e05b45cddd8a47d51f7 Moved silc_client_ch[u]mode[_char] to client library from silc/. NAMES command now shows users modes on joining with nickname --- bb1973faaa81ead23b3d5e05b45cddd8a47d51f7 diff --cc CHANGES index 08341773,00000000..bfec5a99 mode 100644,000000..100644 --- a/CHANGES +++ b/CHANGES @@@ -1,614 -1,0 +1,622 @@@ +Wed Nov 1 17:21:26 EET 2000 Pekka Riikonen + ++ * NAMES command reply now shows users mode with the nickname when ++ joining to channel. ++ ++ * Moved silc_client_ch[u]mode[_char] functions from ++ silc/clientutil.[ch] to lib/silcclient/client.[ch]. Though, that ++ place sucks, they are utility functions and should be in some ++ other file. ++ + * Fixed some unsigned int's to unsigned short's. Patch by cras. + + * Fixed contrib/getopt*.[ch] to not require config.h. Patch by + cras. + +Tue Oct 31 20:10:37 EET 2000 Pekka Riikonen + + * Updated README. + + * Added TRQ (efficient deque and list library) into lib/trq. This is + a very good list library that is currently used in the SILC. Defined + SilcList API over the library because I didn't like the API very + much. See lib/trq/silclist.h for the API and examples of how to + use the API. Fixed various places in the code to use the new + SilcList API. The SilcList is meant for lists that has a structure + already defined as a list. It is not suitable to add just some + context to the list (in TRQ, the context is the list actually). + + So, I defined SilcDList that can be used for the purpose where + predefined list structure does not exit. This can be used as + such list. Now some context just can be added to the SilcDList. + Currently this list is not used in the SILC just yet, though there + are a lot places where this can replace dynamically allocated + tables and I will fix these places, later, to use SilcDList. + See lib/trq/silcdlist.h for SilcDList (they are all inline functions, + and use TRQ internally). + + Also fixed some annoying warning messages that the original TRQ + code generated. Also minor changes to TRQ's Makefile.in. + + * Added support for querying by Client ID to both WHOIS and + IDENTIFY commands into server, as required by the protocol. + + * Removed method function pointers from SilcBuffer structure. They + weren't used to anything and just increased the context size for + no good reason. This change also made silc_buffer_alloc and + silc_buffer_free functions inline functions. + + * Disabled command flooding detection support until it's fixed so + that it accepts commands in but does not execute them more than once + in two seconds. + + * Added silc_net_localhost(), to return local hostname, into + lib/silcutil/silcnet.[ch]. Also added client->hostname pointer + that must be initialized before calling silc_client_init. + + * Added new function: silc_server_send_notify_on_channels to send + notify messages to all channels client has joined. It is assured + that the message is sent only once per client. + + * Moved silc_log_format (from lib/silcutil/silclog.[ch] into + lib/silcutil/silcutil.[ch] as silc_format function. The new + function is generic and is used by server as well, not only by + the logging routines. + + * Added new SKE status type: SILC_SKE_STATUS_BAD_VERSION to indicate + the provided version string was not acceptable. Added new function: + silc_ske_check_version into lib/silcske/silcske.h. The function + must be implemented by the application (client or server) and it + does not reside in the SKE library. The function checks the version + string remote end sent. + + * Added back pointers (to opaque context and to SilcSocketConnection) + into SilcPacketContext structure into lib/silccore/silcpacket.h. + + * Added silc_packet_context_dup into lib/silccore/silcpacket.[ch] to + duplicate packet context structure. + + * Changed `notify' client operation to send same arguments as client + receives from server except for ID's. ID's are mapped to correct + ID entry and that is returned. Also, if channel entry is not sent + by server but the notify is for channel the channel entry is sent + to application (otherwise application doesn't know that it is for + channel (library gets it from packet's Destination ID)). + + * Added silc_client_remove_from_channels into client library to + remove a client from all channels it has joined to. Used when + received SIGNOFF notify from server. Added also new function + silc_client_replace_from_channels to replace old ID entry with + new ID entry on all channels. Used when received NICK_CHANGE + notify from server. + + * Fixed ID Cache list handling in silc_idlist_get_client in + lib/silcclient/idlist.c. Also, added silc_idlist_get_client_by_id + to get (or query) client by ID. + + * Updated TODO list. + + * Added connection authentication status message defined by the + protocol: SILC_CONN_AUTH_OK and SILC_CONN_AUTH_FAILED and added the + support for these into the code in client and server side. + + * Added generic function silc_client_send_command to send any command + with variable argument list. Application should use this function + to send commands if the command functions provided by the library + does not suite for the application's user interface needs. + + * Added new `failure' client operation. Application is notified about + received failure packet if client is executing a protocol. In this + case the protocol's execution has failed. + + * Added SKE's end notify to send the SKE_SUCCESS notify message that + is required by the protocol. + + * Added SILC_PROTOCOL_STATE_FAILURE to indicate received failure + packet from remote. SILC_PROTOCOL_STATE_ERROR indicates local + error at our end. + + * Added status flag to SilcSKE object to indicate realtime status + of the SKE protocol. + + * Application receives now exactly same command reply arguments as + the library receives from server. However, if ID is received the + corresponding ID entry is returned to the application (eg. Client + ID is mapped to correct SilcClientEntry entry and that is returned). + Changed command_reply client operation due to this change. + + * Changed all ID's in commands and in command replys as ID Payloads. + Change affected both client and server side codes. + + All ID's sent in SILC network (with execption of ID's in SILC + Packet header) are sent in ID Payload to support variable length + ID's. + + * Server now notifies nick changes and notifies all clients on + the channels about the new nickname (about the new Client ID, + actually). + + * Implemented CMODE command to change channel modes. Supports all + channel modes defined by the protocol specs except ban and invite + lists. (Also, private channel key mode is supported but support for + setting private channel key in client is missing, thus, this mode + has no effect on client side (except that server requires that the + client uses private channel key and normal channel traffic does not + work anymore)). + + Also, invite mode works per se, but INVITE command does not work + yet correctly, so you can set channel as invite only channel but + inviting clients to the channel does not work (it is yet to be + thought what's the best way to do it). + + * Added new command SILC_COMMAND_CUMODE to change user mode on the + channel. Defined user modes: CHANNEL_FOUNDER and CHANNEL_OPERATOR. + Implemented CUMODE command to change user's mode on the channel. + Supports all modes defined by the protocol specs. + + * Added NAMES command reply to return users modes on the channel. + + * Removed unnecessary and slow ciphers from lib/silccrypt. + + * Set SO_KEEPALIVE option to connection sockets by default. + + * Added new command reply status: SILC_STATUS_USER_NOT_ON_CHANNEL. + + * Added notify types: MOTD, CMODE_CHANGE and CUMODE_CHANGE. Also, + redefined the Notify Payload into protocol specs. + + * Added silc_id_payload_parse_id to get ID directly from raw + ID payload data. + +Mon Oct 9 20:57:02 EEST 2000 Pekka Riikonen + + * Changed SILC_COMMAND_IDENTIFY in protocol specification to + accept searching by Client ID as well. + + * Added support for LEAVE and SIGNOFF notify types in client library. + + * Added silc_id_payload_parse_data into lib/silccore/silcpayload.[ch] + to parse ID Payload from raw data. + +Sun Oct 8 19:33:08 EEST 2000 Pekka Riikonen + + * Added flags parameter into silc_ske_assemble_security_properties + function in lib/silcske/silcske.[ch]. + + * Changed notify client operation to fit better for notify messages + sent by server. The notify payload received from server is now + passed to the application (after parsing it to SilcNotifyPayload). + It is application's responsibility to retrieve the arguments + from the payload and show the message the way it wants. The message + sent by server is implementation specific. + + * Changed public keys to comply with the protocol specification. + Old public keys are not supported anymore and are not compatible. + + * Removed nickname from Channel Payload as the latest draft removed + it. The client must resolve the nickname from the NAMES command + reply received when it joined the channel. + + Also, changed all channel_xxxx_payload to channel_payload_xxxx. + +Sat Oct 7 21:55:01 EEST 2000 Pekka Riikonen + + * Fixed some errors in protocol specification drafts. + + * Created lib/silccore/silcnotify.c to implement Notify Payload + encoding and decoding, lib/silccore/silcpayload.[ch] to implement + generic payloads described by protocol specifications. The file + includes implementations for ID Payload and Argument Payload. + + * Changed Command Payload implementation to use the new Argument + Payload. Changed command_xxxx_payload to command_payload_xxxx + to comply with SILC coding conventions. + + * Added suppport for Argument Payload handling in Notify Payload + implementation as protocol requires it. Added the new support + into server and client lib as well. + +Thu Oct 5 21:16:28 EEST 2000 Pekka Riikonen + + * Added support for multiple nicknames on same channel. [n] is + added locally to the nickname if there are more than one same + nicknames on the channel. + + * Server now sends all nicknames that matched WHOIS request. + Client also shows the list received from server. + + * Added TOPIC command to client side. User can now set and show + current topic on channel. + + * Added MOTD command to client and server. Also, server sends the + motd when client connects to the server. + + * Changed version strings to comply ISO 8601. + +Wed Oct 4 23:29:06 EEST 2000 Pekka Riikonen + + * Fixed protocol error handling in client library. It should now + cope even if the SKE fails for some reason. + + * Made new protocol specification drafts for submitting to IETF. + + * Implemented TOPIC command to server in silcd/command.c. + + * Added two new notify types into lib/silccore/silcnotify.h: + SILC_NOTIFY_TYPE_NICK_CHANGE and SILC_NOTIFY_TYPE_TOPIC_SET to + notify nickname change and topic setting/change on a channel. + + * API change of command_reply operation in client library. The + application gets now the status type received from server as well. + +Sat Sep 30 16:57:42 EEST 2000 Pekka Riikonen + + * Removed the function just added to lib/silcutil/silcschedule.[ch]. + + * Cras fixed and optimized the packet handling even further and + it should work now. Minor change to the prototype of function + silc_packet_receive_process in lib/silccore/silcpacket.[ch]. + +Sat Sep 30 08:48:47 EEST 2000 Pekka Riikonen + + * Added new function into lib/silcutil/silcschedule.[ch]: + silc_schedule_with_fd to select() a specified fd. The function + returns after timeout expires or data arrives or goes. The + function is used by packet routines to wait that all data is + received from network. + + * Fixed data reading from network in lib/silccore/silcpacket.c. + The code now assures that all data is read from the fd and then + continues packet processing. This was a bug fix since the code + used to drop some data in some circumstances. + + * Added new function into lib/silcclient/client.[ch]: + silc_client_start_key_exchange to start key exchange after + connection has been established to server. The code internally + now uses this funtion but its main purpose was to provide it + for applications that perform their own connecting. After + application has created a connection it merely calls this + function to start the key exchange between client and server. + The library takes care of everything else after that. + + Updated also lib/silcclient/README to explain the usage of + this new function. + + * Do not send to application information that connection has + been established. Application gets notified it by connect + operation anyway. + +Thu Sep 28 23:40:19 EEST 2000 Pekka Riikonen + + * Applied cras's patch to add silc_schedule_one function. The + function runs scheduler once and returns. + + * Fixed the scheduler after cras messed it up. The timeout + handling works now as it's supposed to work. + + * Added into lib/silccore/ silcnotify.h to include notify + message types support. Changed silc_server_send_notify* + functions, in server.[ch], to support those new notify types. + Added the support for the notify types into client library, + as well. Added new notify client operation into ops.h in + lib/silcclient/. + + * Changed silc_server_packet_send_to_channel to send normal + packets instead of just channel message packets. The function + is now used to send the notify packets to channels. It is not + used to send channel message packets anymore, as server never + sends them anymore. + + * Added explicit casting into lib/silcutil/silcbuffmt.c to few + va_arg()s as it seems to require it nowadays. I guess, if SILC + is compiled with older va_arg() the new code should work anyway. + +Wed Sep 13 18:10:14 EEST 2000 Pekka Riikonen + + * Splitted core library. Core library (lib/silccore) includes + now only SILC protocol specific core (and common) components. + Created new utility library (lib/silcutil) that includes more + generic purpose stuff. The stuff for util library was taken + from the old core library. This was minor and easy split. + + * Created SILC Client Library (lib/silcclient) that includes + implementation of the SILC client without user interface. This + was major move from silc/ directory. The code has been changed + so that it is transparent towards the user interface. The + silc/ directory includes now the same user interface as before + and it uses the new client library. Read lib/silcclient/README. + Basicly, the client library performs everything else related + to SILC except user interface handling. Also, configuration + files are considered to be part of user interface and library + does not handle them. + + This change also changed a lot of structures, function naming etc. + Most important change was that SilcClientWindow object was + renamed to SilcClientConnection in the client library. Created + also new file lib/silcclient/ops.h. Also added new files + silc/local_command.[ch] and silc/client_ops.[ch]. + + All these changes were made to make it easier for user interface + designers to create what ever user interface for the SILC client + they want. + + It is also expected that the server will be moved to lib + directory as well and SILC Server Library will be created; + sometimes in the future. + + * Removed Local commands from lib/silccore/silccommand.h as + they are application specific and new client library does not + handle any of those anymore. + + * Several functions moved to lib/silcutil/silcutilc.[ch] from + old client implementation in silc/. + + * Added support for callback functions in SILC_LOG_* macros. + Application can now set its own callbacks that will be called + instead of using the default functions that will always print + the debug messages to stderr (or stdout). Also, debugging can + now be disabled by setting silc_debug to FALSE and re-enabled by + setting it to TRUE. Note, that logging will still work even + if debugging is disabled. + + New functions in lib/silcutil/silclog.[ch]: silc_log_set_callbacks, + silc_log_reset_callbacks, silc_log_set_debug_callbacks and + silc_log_reset_debug_callbacks. + + * To enable debugging in silc client one must give now -d + option on command line. + + * Changed silc_schedule_init to automatically allocate task queues + if they are not allocated before calling it. + +Thu Sep 7 10:49:33 EEST 2000 Pekka Riikonen + + * Added GMP 3.1 into math library. + +Sun Aug 20 21:27:26 EEST 2000 Pekka Riikonen + + * Added SILC_PACKET_REMOVE_CHANNEL_USER to remove a client from + a channel in SILC network. The packet is used by servers and + routers to notify other routers that user has left a channel. + This little feature was missing until now. Added the feature + to protocol specification as well. + + Added functions: silc_server_send_remove_channel_user and + silc_server_remove_channel_user into server.[ch]. + + * Added SILC_PACKET_REKEY and SILC_PACKET_REKEY_DONE into + lib/silccore/silcpacket.h. However, they are not implemented + yet. + +Sat Aug 19 23:04:16 EEST 2000 Pekka Riikonen + + * Fixed joining to a channel and sending channel messages + between server and router. The channel message sending should + now work inside a cell. + +Tue Jul 25 20:46:13 EEST 2000 Pekka Riikonen + + * Fixed the private message sending between server and router. + The private message sending should now work inside a cell. + + * Added silc_server_replace_id into server.[ch] to replace + existing ID in the SILC network. + + * Added silc_idlist_find_server_by, silc_idlist_replace_client_id + and silc_idlist_replace_server_id into idlist.[ch] in server. + +Mon Jul 24 18:33:31 EEST 2000 Pekka Riikonen + + * Fixed the server to server connections. Server can again now + connect to router. Router to router connections probably does + not work just yet. + +Thu Jul 20 13:15:01 EEST 2000 Pekka Riikonen + + * Added dynamic protocol registering support. Now protocols can + registered and unregistered on the fly. Patch by cras. + +Wed Jul 19 19:08:46 EEST 2000 Pekka Riikonen + + * Added lib/contrib directory to hold routines that some platforms + don't have but are needed by SILC. + + * Added getopt.c, getopt1.c and getopt.h from GNU C library + into lin/contrib to provide getopt() and getopt_long() for + those who don't have it. + +Tue Jul 18 20:41:20 EEST 2000 Pekka Riikonen + + * Added AWAY command to client. When away message is set and + client receives a private message packet the client automatically + replies to the sender with the away message. + + * Fixed a bug in lib/silcmath/mpbin.c: silc_mp_mp2bin. This + bug seemed to be the cause of recent problems when compiling + with gcc-2.95. + + * Added version detection support to SKE protocol specification + and added the new changes to the SKE implementation as well. + There were other minor changes in the SKE protocol as well. + + Many changes in lib/silcske/silcske.[ch] and in + lib/silcske/payload.[ch]. + + * Added ^U functionality, clear input line. Patch from cras. + +Mon Jul 17 23:33:26 EEST 2000 Pekka Riikonen + + * Mainly small bugfixes on core library. Fixed some debugging + logging and buffer overflow in silclog.c. + + * Updated config.sub and config.guess on the distribution tree. + +Sat Jul 15 15:33:48 EEST 2000 Pekka Riikonen + + * Added command lagging support in server. Client may execute + commands now only once in two seconds. + +Thu Jul 13 22:10:21 EEST 2000 Pekka Riikonen + + * Optimized packet reception. MAC computation and checking is now + also more optimized. A lot previously duplicated code is now + used as generic by both client and server. + + * Fixed key pair generation in clientutil.c + +Wed Jul 12 18:28:07 EEST 2000 Pekka Riikonen + + * Added into lib/silccore/silcbufutil.[ch] new function; + silc_buffer_realloc. + + * Moved generic packet sending/encryption functions to + lib/silccore/silcpacket.[ch] from client and server. Some + rewriting of the functions. + + * Moved all generic packet reception/decryption functions to + lib/silccore/silcpacket.[ch] from client and server. The + packet processing is now much cleaner in both client and server. + These were major changes in both client and server. + + * Created many common functions in server to do packet sending. + Previously code were duplicated a lot, this has been removed + with these changes. + +Tue Jul 11 20:27:26 EEST 2000 Pekka Riikonen + + * Rewrote major parts of the ID cache system. Don't know + whether it is better now or not but at least the API is more + cleaner now. + + * Major rewrite on ID cache stuff on client because of the ID + cache API changes. Added idlist.c to client. + + * Also major rewrite on ID cache stuff on server as well. + Major rewrite of idlist.[ch]. SilcXXXList's are now named + SilcXXXEntry's. We won't keep anymore idlist specific pointers + in hand, instead they are all put into the ID cache system now. + All server_idlist_* routines uses ID cache now instead of + traversing its own lists (those lists does not exist anymore). + SilcIDList though still exists. Also, SilcXXXEntry's are + now pointers. + +Sun Jul 9 15:19:24 EEST 2000 Pekka Riikonen + + * Finally made the SKE implementation compliant to the protocol + specification. All mp integers are now binary encoded as + opposed being HEX encoded. + + * Added lib/silcmath/mpbin.[ch]. Encoding mp intergers to and + from binary data. + + * Added into lib/silccore/silcutil.[ch] PEM encoding/decoding + functions: silc_[encode/decode]_pem. Also added function + silc_encode_pem_file to PEM encode with newlines ('\n') for + saving into a file. + + * SILC public keys are now encoded either PEM or binary. Same + option is for private keys as well. By default private keys + are binary encoded and public keys PEM encoded. Silly HEX + encoding were removed. + + * Added into lib/silccrypt/silchash.[ch] silc_hash_fingerprint + function to create fingerprints. + + * Fixed a bug in SHA1; does not change the original data anymore. + + * Partly implemented INFO command on client and server side. + Fixed CLEAR command. Changes to SERVER command; show current + server(s) when giving command without arguments. Added + VERSION command to client. + + * Added check to server that unregistered connections cannot + execute commands (unless it is specificly allowed). + +Thu Jul 6 18:12:24 EEST 2000 Pekka Riikonen + + * Fixed screen refresh. + + * Fixed channel joining bug from client. On some circumstances + client tried to join to a channel it had already joined. + + * Added public key verification process into client's protocol.c. + The client now verifies the public key from user and saves + it into ~./silc/serverkeys/ directory. + + Added into: clientutil.[ch]: silc_client_verify_server_key. + + * Changed SKE protocol's silc_ske_initiator_finish function + to accept callback function that verifies the received public + key. Removed old silc_ske_verify_public_key function. + +Wed Jul 5 19:19:02 EEST 2000 Pekka Riikonen + + * Added into silcpkcs[ch]: silc_pkcs_public_key[_data]_set and + silc_pkcs_private_key[_data]_set. + + * Made the password and public authentication more cleaner in + server's protocol.c. + + * Removed historic and obsolete protocol `channel_auth' from + both client and server. + + * Removed wrong way of sending command status messages from + server to client in server's command.c. The old way violated + protocol specification. + + Changes to silccore/silccommand.[ch]: removed + silc_command_encode_status_payload -> not needed anymore, + changed silc_command_encode_payload_va to accept extra + argument on variable argument list. The argument type must + now be provided to the function. Also, added new function: + silc_command_encode_reply_payload_va which is same as + normal command_encode_payload_va except command status type + is provided as extra argument. + +Tue Jul 4 18:26:39 EEST 2000 Pekka Riikonen + + * Added ~./silc directory handling. The directory includes the + public and private keys for the client. + + Added silc_client_check_silc_dir, silc_client_create_identifier + and silc_client_load_keys. + + * Implemented SILC protocol compliant public key. Added public + and private key saving to and loading from files. + + Added into silcpkcs.[ch]: silc_pkcs_encode_identifier, + silc_pkcs_public_key_encode[_data], silc_pkcs_public_key_decode, + silc_pkcs_private_key_encode[_data], silc_pkcs_private_key_decode, + silc_pkcs_public_key_alloc, silc_pkcs_public_key_free, + silc_pkcs_private_key_alloc and silc_pkcs_private_key_free. + + Implemented: silc_pkcs_save_[public/private]_key[_data] and + silc_pkcs_load_[public/private]_key. + +Mon Jul 3 18:51:27 EEST 2000 Pekka Riikonen + + * Added silc_server_get_route (route.[ch]) to get connection + data for the fastest route for given ID. + + * Implemented INVITE command on client and server. The command + were re-defined in the SILC Protocol Specification and the + implementation now complies with the specification. + + * Implemented PING command on client and server. + + * Implemented NAMES command on client and server. The server side + supports currently only normal server not router server yet. + Some changes to NAMES definition in SILC protocol specification. + +Sun Jul 2 18:23:01 EEST 2000 Pekka Riikonen + + * Implemented LEAVE command on client and server. + + * Previously deprecated SILC_PACKET_FORWARDED flag is now in use + again. This change was made to the protocol as well. Server + should not violate the protocol specification anymore. + +Fri Jun 30 14:03:26 EEST 2000 Pekka Riikonen + + * Added SOCKS4 and SOCKS5 support to SILC client. SOCKS5 + was tested. SOCKS4 was not but should work anyway. diff --cc apps/silc/clientutil.c index 321160d4,21043795..210f50b0 --- a/apps/silc/clientutil.c +++ b/apps/silc/clientutil.c @@@ -493,316 -441,4 +493,239 @@@ New pair of keys will be created. Plea silc_math_primegen_uninit(); silc_rng_free(rng); silc_pkcs_free(pkcs); + + return TRUE; +} + +/* This checks stats for various SILC files and directories. First it + checks if ~/.silc directory exist and is owned by the correct user. If + it doesn't exist, it will create the directory. After that it checks if + user's Public and Private key files exists and that they aren't expired. + If they doesn't exist or they are expired, they will be (re)created + after return. */ + +int silc_client_check_silc_dir() +{ + char filename[256], file_public_key[256], file_private_key[256]; + char servfilename[256]; + char *identifier; + struct stat st; + struct passwd *pw; + int firstime = FALSE; + time_t curtime, modtime; + + SILC_LOG_DEBUG(("Checking ~./silc directory")); + + memset(filename, 0, sizeof(filename)); + memset(file_public_key, 0, sizeof(file_public_key)); + memset(file_private_key, 0, sizeof(file_private_key)); + + pw = getpwuid(getuid()); + if (!pw) { + fprintf(stderr, "silc: %s\n", strerror(errno)); + return FALSE; + } + + identifier = silc_client_create_identifier(); + + /* We'll take home path from /etc/passwd file to be sure. */ + snprintf(filename, sizeof(filename) - 1, "%s/.silc/", pw->pw_dir); + snprintf(servfilename, sizeof(servfilename) - 1, "%s/.silc/serverkeys", + pw->pw_dir); + + /* + * Check ~/.silc directory + */ + if ((stat(filename, &st)) == -1) { + /* If dir doesn't exist */ + if (errno == ENOENT) { + if (pw->pw_uid == geteuid()) { + if ((mkdir(filename, 0755)) == -1) { + fprintf(stderr, "Couldn't create `%s' directory\n", filename); + return FALSE; + } + + /* Directory was created. First time running SILC */ + firstime = TRUE; + } else { + fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n", + filename); + return FALSE; + } + } else { + fprintf(stderr, "%s\n", strerror(errno)); + return FALSE; + } + } else { + + /* Check the owner of the dir */ + if (st.st_uid != 0 && st.st_uid != pw->pw_uid) { + fprintf(stderr, "You don't seem to own `%s' directory\n", + filename); + return FALSE; + } + + /* Check the permissions of the dir */ + if ((st.st_mode & 0777) != 0755) { + if ((chmod(filename, 0755)) == -1) { + fprintf(stderr, "Permissions for `%s' directory must be 0755\n", + filename); + return FALSE; + } + } + } + + /* + * Check ~./silc/serverkeys directory + */ + if ((stat(servfilename, &st)) == -1) { + /* If dir doesn't exist */ + if (errno == ENOENT) { + if (pw->pw_uid == geteuid()) { + if ((mkdir(servfilename, 0755)) == -1) { + fprintf(stderr, "Couldn't create `%s' directory\n", servfilename); + return FALSE; + } + } else { + fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n", + servfilename); + return FALSE; + } + } else { + fprintf(stderr, "%s\n", strerror(errno)); + return FALSE; + } + } + + /* + * Check Public and Private keys + */ + snprintf(file_public_key, sizeof(file_public_key) - 1, "%s%s", + filename, SILC_CLIENT_PUBLIC_KEY_NAME); + snprintf(file_private_key, sizeof(file_private_key) - 1, "%s%s", + filename, SILC_CLIENT_PRIVATE_KEY_NAME); + + /* If running SILC first time */ + if (firstime) { + fprintf(stdout, "Running SILC for the first time\n"); + silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS, + SILC_CLIENT_DEF_PKCS_LEN, + file_public_key, file_private_key, + identifier, NULL, NULL); + return TRUE; + } + + if ((stat(file_public_key, &st)) == -1) { + /* If file doesn't exist */ + if (errno == ENOENT) { + fprintf(stdout, "Your public key doesn't exist\n"); + silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS, + SILC_CLIENT_DEF_PKCS_LEN, + file_public_key, + file_private_key, identifier, NULL, NULL); + } else { + fprintf(stderr, "%s\n", strerror(errno)); + return FALSE; + } + } + + if ((stat(file_private_key, &st)) == -1) { + /* If file doesn't exist */ + if (errno == ENOENT) { + fprintf(stdout, "Your private key doesn't exist\n"); + silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS, + SILC_CLIENT_DEF_PKCS_LEN, + file_public_key, + file_private_key, identifier, NULL, NULL); + } else { + fprintf(stderr, "%s\n", strerror(errno)); + return FALSE; + } + } + + /* Check the owner of the public key */ + if (st.st_uid != 0 && st.st_uid != pw->pw_uid) { + fprintf(stderr, "You don't seem to own your public key!?\n"); + return FALSE; + } + + /* Check the owner of the private key */ + if (st.st_uid != 0 && st.st_uid != pw->pw_uid) { + fprintf(stderr, "You don't seem to own your private key!?\n"); + return FALSE; + } + + /* Check the permissions for the private key */ + if ((st.st_mode & 0777) != 0600) { + fprintf(stderr, "Wrong permissions in your private key file `%s'!\n" + "Trying to change them ... ", file_private_key); + if ((chmod(file_private_key, 0600)) == -1) { + fprintf(stderr, + "Failed to change permissions for private key file!\n" + "Permissions for your private key file must be 0600.\n"); + return FALSE; + } + fprintf(stderr, "Done.\n\n"); + } + + /* See if the key has expired. */ + modtime = st.st_mtime; /* last modified */ + curtime = time(0) - modtime; + + /* 86400 is seconds in a day. */ + if (curtime >= (86400 * SILC_CLIENT_KEY_EXPIRES)) { + fprintf(stdout, + "--------------------------------------------------\n" + "Your private key has expired and needs to be\n" + "recreated. This will be done automatically now.\n" + "Your new key will expire in %d days from today.\n" + "--------------------------------------------------\n", + SILC_CLIENT_KEY_EXPIRES); + + silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS, + SILC_CLIENT_DEF_PKCS_LEN, + file_public_key, + file_private_key, identifier, NULL, NULL); + } + + if (identifier) + silc_free(identifier); + + return TRUE; +} + +/* Loads public and private key from files. */ + +int silc_client_load_keys(SilcClient client) +{ + char filename[256]; + struct passwd *pw; + + SILC_LOG_DEBUG(("Loading public and private keys")); + + pw = getpwuid(getuid()); + if (!pw) + return FALSE; + + memset(filename, 0, sizeof(filename)); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%s", + pw->pw_dir, SILC_CLIENT_PRIVATE_KEY_NAME); + + if (silc_pkcs_load_private_key(filename, &client->private_key, + SILC_PKCS_FILE_BIN) == FALSE) + if (silc_pkcs_load_private_key(filename, &client->private_key, + SILC_PKCS_FILE_PEM) == FALSE) + return FALSE; + + memset(filename, 0, sizeof(filename)); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%s", + pw->pw_dir, SILC_CLIENT_PUBLIC_KEY_NAME); + + if (silc_pkcs_load_public_key(filename, &client->public_key, + SILC_PKCS_FILE_PEM) == FALSE) + if (silc_pkcs_load_public_key(filename, &client->public_key, + SILC_PKCS_FILE_BIN) == FALSE) + return FALSE; + + return TRUE; } - - /* Parses mode mask and returns the mode as string. */ - - char *silc_client_chmode(unsigned int mode) - { - char string[20]; - - if (!mode) - return NULL; - - memset(string, 0, sizeof(string)); - - if (mode & SILC_CHANNEL_MODE_PRIVATE) - strncat(string, "p", 1); - - if (mode & SILC_CHANNEL_MODE_SECRET) - strncat(string, "s", 1); - - if (mode & SILC_CHANNEL_MODE_PRIVKEY) - strncat(string, "k", 1); - - if (mode & SILC_CHANNEL_MODE_INVITE) - strncat(string, "i", 1); - - if (mode & SILC_CHANNEL_MODE_TOPIC) - strncat(string, "t", 1); - - if (mode & SILC_CHANNEL_MODE_ULIMIT) - strncat(string, "l", 1); - - if (mode & SILC_CHANNEL_MODE_PASSPHRASE) - strncat(string, "a", 1); - - /* Rest of mode is ignored */ - - return strdup(string); - } - - /* Parses channel user mode mask and returns te mode as string */ - - char *silc_client_chumode(unsigned int mode) - { - char string[4]; - - if (!mode) - return NULL; - - memset(string, 0, sizeof(string)); - - if (mode & SILC_CHANNEL_UMODE_CHANFO) - strncat(string, "f", 1); - - if (mode & SILC_CHANNEL_UMODE_CHANOP) - strncat(string, "o", 1); - - return strdup(string); - } - - /* Parses channel user mode and returns it as special mode character. */ - - char *silc_client_chumode_char(unsigned int mode) - { - char string[4]; - - if (!mode) - return NULL; - - memset(string, 0, sizeof(string)); - - if (mode & SILC_CHANNEL_UMODE_CHANFO) - strncat(string, "*", 1); - - if (mode & SILC_CHANNEL_UMODE_CHANOP) - strncat(string, "@", 1); - - return strdup(string); - } diff --cc apps/silc/clientutil.h index b682a127,e18cf2d7..38f0d9e8 --- a/apps/silc/clientutil.h +++ b/apps/silc/clientutil.h @@@ -35,16 -35,6 +35,13 @@@ char *silc_client_get_passphrase(const void silc_client_list_ciphers(); void silc_client_list_hash_funcs(); void silc_client_list_pkcs(); -void silc_client_create_key_pair(char *pkcs_name, int bits); +char *silc_client_create_identifier(); +int silc_client_create_key_pair(char *pkcs_name, int bits, + char *public_key, char *private_key, + char *identifier, + SilcPublicKey *ret_pub_key, + SilcPrivateKey *ret_prv_key); +int silc_client_check_silc_dir(); +int silc_client_load_keys(SilcClient client); - char *silc_client_chmode(unsigned int mode); - char *silc_client_chumode(unsigned int mode); - char *silc_client_chumode_char(unsigned int mode); #endif diff --cc lib/silcclient/client.c index 4cb6fc7a,00000000..44659ada mode 100644,000000..100644 --- a/lib/silcclient/client.c +++ b/lib/silcclient/client.c @@@ -1,2081 -1,0 +1,2158 @@@ +/* + + client.c + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2000 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. + +*/ +/* $Id$ */ + +#include "clientlibincludes.h" + +/* Static task callback prototypes */ +SILC_TASK_CALLBACK(silc_client_connect_to_server_start); +SILC_TASK_CALLBACK(silc_client_connect_to_server_second); +SILC_TASK_CALLBACK(silc_client_connect_to_server_final); +SILC_TASK_CALLBACK(silc_client_packet_process); +SILC_TASK_CALLBACK(silc_client_packet_parse_real); + +static void silc_client_packet_parse(SilcPacketParserContext *parser_context); +static void silc_client_packet_parse_type(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet); + +/* Allocates new client object. This has to be done before client may + work. After calling this one must call silc_client_init to initialize + the client. The `application' is application specific user data pointer + and caller must free it. */ + +SilcClient silc_client_alloc(SilcClientOperations *ops, void *application) +{ + SilcClient new_client; + + new_client = silc_calloc(1, sizeof(*new_client)); + new_client->application = application; + new_client->ops = ops; + + return new_client; +} + +/* Free's client object */ + +void silc_client_free(SilcClient client) +{ + if (client) { + silc_free(client); + } +} + +/* Initializes the client. This makes all the necessary steps to make + the client ready to be run. One must call silc_client_run to run the + client. */ + +int silc_client_init(SilcClient client) +{ + SILC_LOG_DEBUG(("Initializing client")); + + /* Initialize hash functions for client to use */ + silc_hash_alloc("md5", &client->md5hash); + silc_hash_alloc("sha1", &client->sha1hash); + + /* Initialize none cipher */ + silc_cipher_alloc("none", &client->none_cipher); + + /* Initialize random number generator */ + client->rng = silc_rng_alloc(); + silc_rng_init(client->rng); + silc_math_primegen_init(); /* XXX */ + + /* Register protocols */ + silc_client_protocols_register(); + + /* Initialize the scheduler */ + silc_schedule_init(&client->io_queue, &client->timeout_queue, + &client->generic_queue, 5000); + + return TRUE; +} + +/* Stops the client. This is called to stop the client and thus to stop + the program. */ + +void silc_client_stop(SilcClient client) +{ + SILC_LOG_DEBUG(("Stopping client")); + + /* Stop the scheduler, although it might be already stopped. This + doesn't hurt anyone. This removes all the tasks and task queues, + as well. */ + silc_schedule_stop(); + silc_schedule_uninit(); + + silc_client_protocols_unregister(); + + SILC_LOG_DEBUG(("Client stopped")); +} + +/* Runs the client. */ + +void silc_client_run(SilcClient client) +{ + SILC_LOG_DEBUG(("Running client")); + + /* Start the scheduler, the heart of the SILC client. When this returns + the program will be terminated. */ + silc_schedule(); +} + +/* Allocates and adds new connection to the client. This adds the allocated + connection to the connection table and returns a pointer to it. A client + can have multiple connections to multiple servers. Every connection must + be added to the client using this function. User data `context' may + be sent as argument. */ + +SilcClientConnection silc_client_add_connection(SilcClient client, + char *hostname, + int port, + void *context) +{ + SilcClientConnection conn; + int i; + + conn = silc_calloc(1, sizeof(*conn)); + + /* Initialize ID caches */ + conn->client_cache = silc_idcache_alloc(0); + conn->channel_cache = silc_idcache_alloc(0); + conn->server_cache = silc_idcache_alloc(0); + conn->client = client; + conn->remote_host = strdup(hostname); + conn->remote_port = port; + conn->context = context; + + /* Add the connection to connections table */ + for (i = 0; i < client->conns_count; i++) + if (client->conns && !client->conns[i]) { + client->conns[i] = conn; + return conn; + } + + client->conns = silc_realloc(client->conns, sizeof(*client->conns) + * (client->conns_count + 1)); + client->conns[client->conns_count] = conn; + client->conns_count++; + + return conn; +} + +/* Removes connection from client. */ + +void silc_client_del_connection(SilcClient client, SilcClientConnection conn) +{ + int i; + + for (i = 0; i < client->conns_count; i++) + if (client->conns[i] == conn) { + silc_free(conn); + client->conns[i] = NULL; + } +} + +/* Internal context for connection process. This is needed as we + doing asynchronous connecting. */ +typedef struct { + SilcClient client; + SilcClientConnection conn; + SilcTask task; + int sock; + char *host; + int port; + int tries; +} SilcClientInternalConnectContext; + +static int +silc_client_connect_to_server_internal(SilcClientInternalConnectContext *ctx) +{ + int sock; + + /* XXX In the future we should give up this non-blocking connect all + together and use threads instead. */ + /* Create connection to server asynchronously */ + sock = silc_net_create_connection_async(ctx->port, ctx->host); + if (sock < 0) + return -1; + + /* Register task that will receive the async connect and will + read the result. */ + ctx->task = silc_task_register(ctx->client->io_queue, sock, + silc_client_connect_to_server_start, + (void *)ctx, 0, 0, + SILC_TASK_FD, + SILC_TASK_PRI_NORMAL); + silc_task_reset_iotype(ctx->task, SILC_TASK_WRITE); + silc_schedule_set_listen_fd(sock, ctx->task->iomask); + + ctx->sock = sock; + + return sock; +} + +/* Connects to remote server. This is the main routine used to connect + to SILC server. Returns -1 on error and the created socket otherwise. + The `context' is user context that is saved into the SilcClientConnection + that is created after the connection is created. */ + +int silc_client_connect_to_server(SilcClient client, int port, + char *host, void *context) +{ + SilcClientInternalConnectContext *ctx; + SilcClientConnection conn; + int sock; + + SILC_LOG_DEBUG(("Connecting to port %d of server %s", + port, host)); + + conn = silc_client_add_connection(client, host, port, context); + + client->ops->say(client, conn, + "Connecting to port %d of server %s", port, host); + + /* Allocate internal context for connection process. This is + needed as we are doing async connecting. */ + ctx = silc_calloc(1, sizeof(*ctx)); + ctx->client = client; + ctx->conn = conn; + ctx->host = strdup(host); + ctx->port = port; + ctx->tries = 0; + + /* Do the actual connecting process */ + sock = silc_client_connect_to_server_internal(ctx); + if (sock == -1) + silc_client_del_connection(client, conn); + return sock; +} + +/* Start SILC Key Exchange (SKE) protocol to negotiate shared secret + key material between client and server. This function can be called + directly if application is performing its own connecting and does not + use the connecting provided by this library. */ + +int silc_client_start_key_exchange(SilcClient client, + SilcClientConnection conn, + int fd) +{ + SilcProtocol protocol; + SilcClientKEInternalContext *proto_ctx; + void *context; + + /* Allocate new socket connection object */ + silc_socket_alloc(fd, SILC_SOCKET_TYPE_SERVER, (void *)conn, &conn->sock); + if (conn->sock == NULL) { + client->ops->say(client, conn, + "Error: Could not allocate connection socket"); + return FALSE; + } + + conn->nickname = strdup(client->username); + conn->sock->hostname = conn->remote_host; + conn->sock->port = conn->remote_port; + + /* Allocate internal Key Exchange context. This is sent to the + protocol as context. */ + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->client = (void *)client; + proto_ctx->sock = conn->sock; + proto_ctx->rng = client->rng; + proto_ctx->responder = FALSE; + + /* Perform key exchange protocol. silc_client_connect_to_server_final + will be called after the protocol is finished. */ + silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, + &protocol, (void *)proto_ctx, + silc_client_connect_to_server_second); + if (!protocol) { + client->ops->say(client, conn, + "Error: Could not start authentication protocol"); + return FALSE; + } + conn->sock->protocol = protocol; + + /* Register the connection for network input and output. This sets + that scheduler will listen for incoming packets for this connection + and sets that outgoing packets may be sent to this connection as well. + However, this doesn't set the scheduler for outgoing traffic, it will + be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT, + later when outgoing data is available. */ + context = (void *)client; + SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(fd); + + /* Execute the protocol */ + protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); + return TRUE; +} + +/* Start of the connection to the remote server. This is called after + succesful TCP/IP connection has been established to the remote host. */ + +SILC_TASK_CALLBACK(silc_client_connect_to_server_start) +{ + SilcClientInternalConnectContext *ctx = + (SilcClientInternalConnectContext *)context; + SilcClient client = ctx->client; + SilcClientConnection conn = ctx->conn; + int opt, opt_len = sizeof(opt); + + SILC_LOG_DEBUG(("Start")); + + /* Check the socket status as it might be in error */ + getsockopt(fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len); + if (opt != 0) { + if (ctx->tries < 2) { + /* Connection failed but lets try again */ + client->ops->say(client, conn, "Could not connect to server %s: %s", + ctx->host, strerror(opt)); + client->ops->say(client, conn, + "Connecting to port %d of server %s resumed", + ctx->port, ctx->host); + + /* Unregister old connection try */ + silc_schedule_unset_listen_fd(fd); + silc_net_close_connection(fd); + silc_task_unregister(client->io_queue, ctx->task); + + /* Try again */ + silc_client_connect_to_server_internal(ctx); + ctx->tries++; + } else { + /* Connection failed and we won't try anymore */ + client->ops->say(client, conn, "Could not connect to server %s: %s", + ctx->host, strerror(opt)); + silc_schedule_unset_listen_fd(fd); + silc_net_close_connection(fd); + silc_task_unregister(client->io_queue, ctx->task); + silc_free(ctx); + + /* Notify application of failure */ + client->ops->connect(client, conn, FALSE); + silc_client_del_connection(client, conn); + } + return; + } + + silc_schedule_unset_listen_fd(fd); + silc_task_unregister(client->io_queue, ctx->task); + silc_free(ctx); + + if (!silc_client_start_key_exchange(client, conn, fd)) { + silc_net_close_connection(fd); + client->ops->connect(client, conn, FALSE); + } +} + +/* Second part of the connecting to the server. This executed + authentication protocol. */ + +SILC_TASK_CALLBACK(silc_client_connect_to_server_second) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcClientKEInternalContext *ctx = + (SilcClientKEInternalContext *)protocol->context; + SilcClient client = (SilcClient)ctx->client; + SilcSocketConnection sock = NULL; + SilcClientConnAuthInternalContext *proto_ctx; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_ERROR) { + /* Error occured during protocol */ + SILC_LOG_DEBUG(("Error during KE protocol")); + silc_protocol_free(protocol); + if (ctx->ske) + silc_ske_free(ctx->ske); + if (ctx->dest_id) + silc_free(ctx->dest_id); + ctx->sock->protocol = NULL; + + /* Notify application of failure */ + client->ops->connect(client, ctx->sock->user_data, FALSE); + silc_free(ctx); + return; + } + + /* Allocate internal context for the authentication protocol. This + is sent as context for the protocol. */ + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->client = (void *)client; + proto_ctx->sock = sock = ctx->sock; + proto_ctx->ske = ctx->ske; /* Save SKE object from previous protocol */ + proto_ctx->dest_id_type = ctx->dest_id_type; + proto_ctx->dest_id = ctx->dest_id; + + /* Resolve the authentication method to be used in this connection */ + if (!client->ops->get_auth_method(client, sock->user_data, sock->hostname, + sock->port, &proto_ctx->auth_meth, + &proto_ctx->auth_data, + &proto_ctx->auth_data_len)) + { + /* XXX do AUTH_REQUEST resolcing with server */ + proto_ctx->auth_meth = SILC_PROTOCOL_CONN_AUTH_NONE; + } + + /* Free old protocol as it is finished now */ + silc_protocol_free(protocol); + if (ctx->packet) + silc_buffer_free(ctx->packet); + silc_free(ctx); + /* silc_free(ctx->keymat....); */ + sock->protocol = NULL; + + /* Allocate the authentication protocol. This is allocated here + but we won't start it yet. We will be receiving party of this + protocol thus we will wait that connecting party will make + their first move. */ + silc_protocol_alloc(SILC_PROTOCOL_CLIENT_CONNECTION_AUTH, + &sock->protocol, (void *)proto_ctx, + silc_client_connect_to_server_final); + + /* Execute the protocol */ + sock->protocol->execute(client->timeout_queue, 0, sock->protocol, fd, 0, 0); +} + +/* Finalizes the connection to the remote SILC server. This is called + after authentication protocol has been completed. This send our + user information to the server to receive our client ID from + server. */ + +SILC_TASK_CALLBACK(silc_client_connect_to_server_final) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcClientConnAuthInternalContext *ctx = + (SilcClientConnAuthInternalContext *)protocol->context; + SilcClient client = (SilcClient)ctx->client; + SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data; + SilcBuffer packet; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_ERROR) { + /* Error occured during protocol */ + SILC_LOG_DEBUG(("Error during authentication protocol")); + silc_protocol_free(protocol); + if (ctx->auth_data) + silc_free(ctx->auth_data); + if (ctx->ske) + silc_ske_free(ctx->ske); + if (ctx->dest_id) + silc_free(ctx->dest_id); + conn->sock->protocol = NULL; + + /* Notify application of failure */ + client->ops->connect(client, ctx->sock->user_data, FALSE); + silc_free(ctx); + return; + } + + /* Send NEW_CLIENT packet to the server. We will become registered + to the SILC network after sending this packet and we will receive + client ID from the server. */ + packet = silc_buffer_alloc(2 + 2 + strlen(client->username) + + strlen(client->realname)); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + silc_buffer_format(packet, + SILC_STR_UI_SHORT(strlen(client->username)), + SILC_STR_UI_XNSTRING(client->username, + strlen(client->username)), + SILC_STR_UI_SHORT(strlen(client->realname)), + SILC_STR_UI_XNSTRING(client->realname, + strlen(client->realname)), + SILC_STR_END); + + /* Send the packet */ + silc_client_packet_send(client, ctx->sock, SILC_PACKET_NEW_CLIENT, + NULL, 0, NULL, NULL, + packet->data, packet->len, TRUE); + silc_buffer_free(packet); + + /* Save remote ID. */ + conn->remote_id = ctx->dest_id; + conn->remote_id_data = silc_id_id2str(ctx->dest_id, SILC_ID_SERVER); + conn->remote_id_data_len = SILC_ID_SERVER_LEN; + + /* Notify application of successful connection */ + client->ops->connect(client, conn, TRUE); + + silc_protocol_free(protocol); + if (ctx->auth_data) + silc_free(ctx->auth_data); + if (ctx->ske) + silc_ske_free(ctx->ske); + if (ctx->dest_id) + silc_free(ctx->dest_id); + silc_free(ctx); + conn->sock->protocol = NULL; +} + +/* Internal routine that sends packet or marks packet to be sent. This + is used directly only in special cases. Normal cases should use + silc_server_packet_send. Returns < 0 on error. */ + +static int silc_client_packet_send_real(SilcClient client, + SilcSocketConnection sock, + int force_send) +{ + int ret; + + /* Send the packet */ + ret = silc_packet_send(sock, force_send); + if (ret != -2) + return ret; + + /* Mark that there is some outgoing data available for this connection. + This call sets the connection both for input and output (the input + is set always and this call keeps the input setting, actually). + Actual data sending is performed by silc_client_packet_process. */ + SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT(sock->sock); + + /* Mark to socket that data is pending in outgoing buffer. This flag + is needed if new data is added to the buffer before the earlier + put data is sent to the network. */ + SILC_SET_OUTBUF_PENDING(sock); + + return 0; +} + +/* Packet processing callback. This is used to send and receive packets + from network. This is generic task. */ + +SILC_TASK_CALLBACK(silc_client_packet_process) +{ + SilcClient client = (SilcClient)context; + SilcSocketConnection sock = NULL; + SilcClientConnection conn; + int ret; + + SILC_LOG_DEBUG(("Processing packet")); + + SILC_CLIENT_GET_SOCK(client, fd, sock); + if (sock == NULL) + return; + + conn = (SilcClientConnection)sock->user_data; + + /* Packet sending */ + if (type == SILC_TASK_WRITE) { + SILC_LOG_DEBUG(("Writing data to connection")); + + if (sock->outbuf->data - sock->outbuf->head) + silc_buffer_push(sock->outbuf, + sock->outbuf->data - sock->outbuf->head); + + ret = silc_client_packet_send_real(client, sock, TRUE); + + /* If returned -2 could not write to connection now, will do + it later. */ + if (ret == -2) + return; + + /* The packet has been sent and now it is time to set the connection + back to only for input. When there is again some outgoing data + available for this connection it will be set for output as well. + This call clears the output setting and sets it only for input. */ + SILC_CLIENT_SET_CONNECTION_FOR_INPUT(fd); + SILC_UNSET_OUTBUF_PENDING(sock); + + silc_buffer_clear(sock->outbuf); + return; + } + + /* Packet receiving */ + if (type == SILC_TASK_READ) { + SILC_LOG_DEBUG(("Reading data from connection")); + + /* Read data from network */ + ret = silc_packet_receive(sock); + if (ret < 0) + return; + + /* EOF */ + if (ret == 0) { + SILC_LOG_DEBUG(("Read EOF")); + + /* If connection is disconnecting already we will finally + close the connection */ + if (SILC_IS_DISCONNECTING(sock)) { + client->ops->disconnect(client, conn); + silc_client_close_connection(client, sock); + return; + } + + client->ops->say(client, conn, "Connection closed: premature EOF"); + SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock)); + client->ops->disconnect(client, conn); + silc_client_close_connection(client, sock); + return; + } + + /* Process the packet. This will call the parser that will then + decrypt and parse the packet. */ + silc_packet_receive_process(sock, conn->receive_key, conn->hmac, + silc_client_packet_parse, client); + } +} + +/* Parses whole packet, received earlier. */ + +SILC_TASK_CALLBACK(silc_client_packet_parse_real) +{ + SilcPacketParserContext *parse_ctx = (SilcPacketParserContext *)context; + SilcClient client = (SilcClient)parse_ctx->context; + SilcPacketContext *packet = parse_ctx->packet; + SilcBuffer buffer = packet->buffer; + SilcSocketConnection sock = parse_ctx->sock; + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + int ret; + + SILC_LOG_DEBUG(("Start")); + + /* Decrypt the received packet */ + ret = silc_packet_decrypt(conn->receive_key, conn->hmac, buffer, packet); + if (ret < 0) + goto out; + + if (ret == 0) { + /* Parse the packet. Packet type is returned. */ + ret = silc_packet_parse(packet); + } else { + /* Parse the packet header in special way as this is "special" + packet type. */ + ret = silc_packet_parse_special(packet); + } + + if (ret == SILC_PACKET_NONE) + goto out; + + /* Parse the incoming packet type */ + silc_client_packet_parse_type(client, sock, packet); + + out: + silc_buffer_clear(buffer); + if (packet->src_id) + silc_free(packet->src_id); + if (packet->dst_id) + silc_free(packet->dst_id); + silc_free(packet); + silc_free(parse_ctx); +} + +/* Parser callback called by silc_packet_receive_process. Thie merely + registers timeout that will handle the actual parsing when appropriate. */ + +void silc_client_packet_parse(SilcPacketParserContext *parser_context) +{ + SilcClient client = (SilcClient)parser_context->context; + + /* Parse the packet */ + silc_task_register(client->timeout_queue, parser_context->sock->sock, + silc_client_packet_parse_real, + (void *)parser_context, 0, 1, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); +} + +/* Parses the packet type and calls what ever routines the packet type + requires. This is done for all incoming packets. */ + +void silc_client_packet_parse_type(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcPacketType type = packet->type; + + SILC_LOG_DEBUG(("Parsing packet type %d", type)); + + /* Parse the packet type */ + switch(type) { + case SILC_PACKET_DISCONNECT: + silc_client_disconnected_by_server(client, sock, buffer); + break; + case SILC_PACKET_SUCCESS: + /* + * Success received for something. For now we can have only + * one protocol for connection executing at once hence this + * success message is for whatever protocol is executing currently. + */ + if (sock->protocol) { + sock->protocol->execute(client->timeout_queue, 0, + sock->protocol, sock->sock, 0, 0); + } + break; + case SILC_PACKET_FAILURE: + /* + * Failure received for some protocol. Set the protocol state to + * error and call the protocol callback. This fill cause error on + * protocol and it will call the final callback. + */ + if (sock->protocol) { + sock->protocol->state = SILC_PROTOCOL_STATE_FAILURE; + sock->protocol->execute(client->timeout_queue, 0, + sock->protocol, sock->sock, 0, 0); + + /* XXX We have only two protocols currently thus we know what this + failure indication is. */ + if (buffer->len >= 4) { + unsigned int failure; + + SILC_GET32_MSB(failure, buffer->data); + + /* Notify application */ + client->ops->failure(client, sock->user_data, sock->protocol, + (void *)failure); + } + } + break; + case SILC_PACKET_REJECT: + break; + + case SILC_PACKET_NOTIFY: + /* + * Received notify message + */ + silc_client_notify_by_server(client, sock, packet); + break; + + case SILC_PACKET_ERROR: + /* + * Received error message + */ + silc_client_error_by_server(client, sock, buffer); + break; + + case SILC_PACKET_CHANNEL_MESSAGE: + /* + * Received message to (from, actually) a channel + */ + silc_client_channel_message(client, sock, packet); + break; + case SILC_PACKET_CHANNEL_KEY: + /* + * Received key for a channel. By receiving this key the client will be + * able to talk to the channel it has just joined. This can also be + * a new key for existing channel as keys expire peridiocally. + */ + silc_client_receive_channel_key(client, sock, buffer); + break; + + case SILC_PACKET_PRIVATE_MESSAGE: + /* + * Received private message + */ + silc_client_private_message(client, sock, packet); + break; + case SILC_PACKET_PRIVATE_MESSAGE_KEY: + /* + * Received private message key + */ + break; + + case SILC_PACKET_COMMAND_REPLY: + /* + * Recived reply for a command + */ + silc_client_command_reply_process(client, sock, packet); + break; + + case SILC_PACKET_KEY_EXCHANGE: + if (sock->protocol) { + SilcClientKEInternalContext *proto_ctx = + (SilcClientKEInternalContext *)sock->protocol->context; + + proto_ctx->packet = buffer; + proto_ctx->dest_id_type = packet->src_id_type; + proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type); + + /* Let the protocol handle the packet */ + sock->protocol->execute(client->timeout_queue, 0, + sock->protocol, sock->sock, 0, 0); + } else { + SILC_LOG_ERROR(("Received Key Exchange packet but no key exchange " + "protocol active, packet dropped.")); + + /* XXX Trigger KE protocol?? Rekey actually! */ + } + break; + + case SILC_PACKET_KEY_EXCHANGE_1: + if (sock->protocol) { + + } else { + SILC_LOG_ERROR(("Received Key Exchange 1 packet but no key exchange " + "protocol active, packet dropped.")); + } + break; + case SILC_PACKET_KEY_EXCHANGE_2: + if (sock->protocol) { + SilcClientKEInternalContext *proto_ctx = + (SilcClientKEInternalContext *)sock->protocol->context; + + if (proto_ctx->packet) + silc_buffer_free(proto_ctx->packet); + + proto_ctx->packet = buffer; + proto_ctx->dest_id_type = packet->src_id_type; + proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type); + + /* Let the protocol handle the packet */ + sock->protocol->execute(client->timeout_queue, 0, + sock->protocol, sock->sock, 0, 0); + } else { + SILC_LOG_ERROR(("Received Key Exchange 2 packet but no key exchange " + "protocol active, packet dropped.")); + } + break; + + case SILC_PACKET_NEW_ID: + { + /* + * Received new ID from server. This packet is received at + * the connection to the server. New ID is also received when + * user changes nickname but in that case the new ID is received + * as command reply and not as this packet type. + */ + SilcIDPayload idp; + + idp = silc_id_payload_parse(buffer); + if (silc_id_payload_get_type(idp) != SILC_ID_CLIENT) + break; + + silc_client_receive_new_id(client, sock, idp); + silc_id_payload_free(idp); + break; + } + + default: + SILC_LOG_DEBUG(("Incorrect packet type %d, packet dropped", type)); + break; + } +} + +/* Sends packet. This doesn't actually send the packet instead it assembles + it and marks it to be sent. However, if force_send is TRUE the packet + is sent immediately. if dst_id, cipher and hmac are NULL those parameters + will be derived from sock argument. Otherwise the valid arguments sent + are used. */ + +void silc_client_packet_send(SilcClient client, + SilcSocketConnection sock, + SilcPacketType type, + void *dst_id, + SilcIdType dst_id_type, + SilcCipher cipher, + SilcHmac hmac, + unsigned char *data, + unsigned int data_len, + int force_send) +{ + SilcPacketContext packetdata; + + SILC_LOG_DEBUG(("Sending packet, type %d", type)); + + /* Get data used in the packet sending, keys and stuff */ + if ((!cipher || !hmac || !dst_id) && sock->user_data) { + if (!cipher && ((SilcClientConnection)sock->user_data)->send_key) + cipher = ((SilcClientConnection)sock->user_data)->send_key; + + if (!hmac && ((SilcClientConnection)sock->user_data)->hmac) + hmac = ((SilcClientConnection)sock->user_data)->hmac; + + if (!dst_id && ((SilcClientConnection)sock->user_data)->remote_id) { + dst_id = ((SilcClientConnection)sock->user_data)->remote_id; + dst_id_type = SILC_ID_SERVER; + } + } + + /* Set the packet context pointers */ + packetdata.flags = 0; + packetdata.type = type; + if (((SilcClientConnection)sock->user_data)->local_id_data) + packetdata.src_id = ((SilcClientConnection)sock->user_data)->local_id_data; + else + packetdata.src_id = silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char)); + packetdata.src_id_len = SILC_ID_CLIENT_LEN; + packetdata.src_id_type = SILC_ID_CLIENT; + if (dst_id) { + packetdata.dst_id = silc_id_id2str(dst_id, dst_id_type); + packetdata.dst_id_len = silc_id_get_len(dst_id_type); + packetdata.dst_id_type = dst_id_type; + } else { + packetdata.dst_id = NULL; + packetdata.dst_id_len = 0; + packetdata.dst_id_type = SILC_ID_NONE; + } + packetdata.rng = client->rng; + packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + packetdata.dst_id_len; + packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen); + + /* Prepare outgoing data buffer for packet sending */ + silc_packet_send_prepare(sock, + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + + packetdata.dst_id_len, + packetdata.padlen, + data_len); + + SILC_LOG_DEBUG(("Putting data to outgoing buffer, len %d", data_len)); + + packetdata.buffer = sock->outbuf; + + /* Put the data to the buffer */ + if (data && data_len) + silc_buffer_put(sock->outbuf, data, data_len); + + /* Create the outgoing packet */ + silc_packet_assemble(&packetdata); + + /* Encrypt the packet */ + if (cipher) + silc_packet_encrypt(cipher, hmac, sock->outbuf, sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Packet, len %d", sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_client_packet_send_real(client, sock, force_send); +} + +/* Sends packet to a channel. Packet to channel is always encrypted + differently from "normal" packets. SILC header of the packet is + encrypted with the next receiver's key and the rest of the packet is + encrypted with the channel specific key. Padding and HMAC is computed + with the next receiver's key. */ + +void silc_client_packet_send_to_channel(SilcClient client, + SilcSocketConnection sock, + SilcChannelEntry channel, + unsigned char *data, + unsigned int data_len, + int force_send) +{ + int i; + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcBuffer payload; + SilcPacketContext packetdata; + SilcCipher cipher; + SilcHmac hmac; + unsigned char *id_string; + + SILC_LOG_DEBUG(("Sending packet to channel")); + + if (!channel || !channel->key) { + client->ops->say(client, conn, + "Cannot talk to channel: key does not exist"); + return; + } + + /* Generate IV */ + if (!channel->iv) + for (i = 0; i < 16; i++) + channel->iv[i] = silc_rng_get_byte(client->rng); + else + silc_hash_make(client->md5hash, channel->iv, 16, channel->iv); + + /* Encode the channel payload */ + payload = silc_channel_payload_encode(data_len, data, 16, channel->iv, + client->rng); + if (!payload) { + client->ops->say(client, conn, + "Error: Could not create packet to be sent to channel"); + return; + } + + /* Get data used in packet header encryption, keys and stuff. Rest + of the packet (the payload) is, however, encrypted with the + specified channel key. */ + cipher = conn->send_key; + hmac = conn->hmac; + id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + + /* Set the packet context pointers. The destination ID is always + the Channel ID of the channel. Server and router will handle the + distribution of the packet. */ + packetdata.flags = 0; + packetdata.type = SILC_PACKET_CHANNEL_MESSAGE; + packetdata.src_id = conn->local_id_data; + packetdata.src_id_len = SILC_ID_CLIENT_LEN; + packetdata.src_id_type = SILC_ID_CLIENT; + packetdata.dst_id = id_string; + packetdata.dst_id_len = SILC_ID_CHANNEL_LEN; + packetdata.dst_id_type = SILC_ID_CHANNEL; + packetdata.rng = client->rng; + packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + packetdata.dst_id_len; + packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + + packetdata.dst_id_len)); + + /* Prepare outgoing data buffer for packet sending */ + silc_packet_send_prepare(sock, + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + + packetdata.dst_id_len, + packetdata.padlen, + payload->len); + + packetdata.buffer = sock->outbuf; + + /* Encrypt payload of the packet. This is encrypted with the channel key. */ + channel->channel_key->cipher->encrypt(channel->channel_key->context, + payload->data, payload->data, + payload->len - 16, /* -IV_LEN */ + channel->iv); + + /* Put the actual encrypted payload data into the buffer. */ + silc_buffer_put(sock->outbuf, payload->data, payload->len); + + /* Create the outgoing packet */ + silc_packet_assemble(&packetdata); + + /* Encrypt the header and padding of the packet. This is encrypted + with normal session key shared with our server. */ + silc_packet_encrypt(cipher, hmac, sock->outbuf, SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + packetdata.dst_id_len + + packetdata.padlen); + + SILC_LOG_HEXDUMP(("Packet to channel, len %d", sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_client_packet_send_real(client, sock, force_send); + silc_buffer_free(payload); + silc_free(id_string); +} + +/* Sends private message to remote client. If private message key has + not been set with this client then the message will be encrypted using + normal session keys. Private messages are special packets in SILC + network hence we need this own function for them. This is similiar + to silc_client_packet_send_to_channel except that we send private + message. */ + +void silc_client_packet_send_private_message(SilcClient client, + SilcSocketConnection sock, + SilcClientEntry client_entry, + unsigned char *data, + unsigned int data_len, + int force_send) +{ + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcBuffer buffer; + SilcPacketContext packetdata; + unsigned int nick_len; + SilcCipher cipher; + SilcHmac hmac; + + SILC_LOG_DEBUG(("Sending private message")); + + /* Create private message payload */ + nick_len = strlen(conn->nickname); + buffer = silc_buffer_alloc(2 + nick_len + data_len); + silc_buffer_pull_tail(buffer, SILC_BUFFER_END(buffer)); + silc_buffer_format(buffer, + SILC_STR_UI_SHORT(nick_len), + SILC_STR_UI_XNSTRING(conn->nickname, + nick_len), + SILC_STR_UI_XNSTRING(data, data_len), + SILC_STR_END); + + /* If we don't have private message specific key then private messages + are just as any normal packet thus call normal packet sending. If + the key exist then the encryption process is a bit different and + will be done in the rest of this function. */ + if (!client_entry->send_key) { + silc_client_packet_send(client, sock, SILC_PACKET_PRIVATE_MESSAGE, + client_entry->id, SILC_ID_CLIENT, NULL, NULL, + buffer->data, buffer->len, force_send); + goto out; + } + + /* We have private message specific key */ + + /* Get data used in the encryption */ + cipher = client_entry->send_key; + hmac = conn->hmac; + + /* Set the packet context pointers. */ + packetdata.flags = 0; + packetdata.type = SILC_PACKET_PRIVATE_MESSAGE; + packetdata.src_id = conn->local_id_data; + packetdata.src_id_len = SILC_ID_CLIENT_LEN; + packetdata.src_id_type = SILC_ID_CLIENT; + if (client_entry) + packetdata.dst_id = silc_id_id2str(client_entry->id, SILC_ID_CLIENT); + else + packetdata.dst_id = conn->local_id_data; + packetdata.dst_id_len = SILC_ID_CLIENT_LEN; + packetdata.dst_id_type = SILC_ID_CLIENT; + packetdata.rng = client->rng; + packetdata.truelen = buffer->len + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + packetdata.dst_id_len; + packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + + packetdata.dst_id_len)); + + /* Prepare outgoing data buffer for packet sending */ + silc_packet_send_prepare(sock, + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + + packetdata.dst_id_len, + packetdata.padlen, + buffer->len); + + packetdata.buffer = sock->outbuf; + + /* Encrypt payload of the packet. Encrypt with private message specific + key if it exist, otherwise with session key. */ + cipher->cipher->encrypt(cipher->context, buffer->data, buffer->data, + buffer->len, cipher->iv); + + /* Put the actual encrypted payload data into the buffer. */ + silc_buffer_put(sock->outbuf, buffer->data, buffer->len); + + /* Create the outgoing packet */ + silc_packet_assemble(&packetdata); + + /* Encrypt the header and padding of the packet. */ + silc_packet_encrypt(cipher, hmac, sock->outbuf, SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + packetdata.dst_id_len + + packetdata.padlen); + + SILC_LOG_HEXDUMP(("Private message packet, len %d", sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_client_packet_send_real(client, sock, force_send); + silc_free(packetdata.dst_id); + + out: + silc_free(buffer); +} + +/* Closes connection to remote end. Free's all allocated data except + for some information such as nickname etc. that are valid at all time. */ + +void silc_client_close_connection(SilcClient client, + SilcSocketConnection sock) +{ + SilcClientConnection conn; + + /* We won't listen for this connection anymore */ + silc_schedule_unset_listen_fd(sock->sock); + + /* Unregister all tasks */ + silc_task_unregister_by_fd(client->io_queue, sock->sock); + silc_task_unregister_by_fd(client->timeout_queue, sock->sock); + + /* Close the actual connection */ + silc_net_close_connection(sock->sock); + + client->ops->say(client, sock->user_data, + "Closed connection to host %s", sock->hostname ? + sock->hostname : sock->ip); + + /* Free everything */ + if (sock->user_data) { + conn = (SilcClientConnection)sock->user_data; + + /* XXX Free all client entries and channel entries. */ + + /* Clear ID caches */ + silc_idcache_del_all(conn->client_cache); + silc_idcache_del_all(conn->channel_cache); + + /* Free data */ + if (conn->remote_host) + silc_free(conn->remote_host); + if (conn->local_id) + silc_free(conn->local_id); + if (conn->local_id_data) + silc_free(conn->local_id_data); + if (conn->send_key) + silc_cipher_free(conn->send_key); + if (conn->receive_key) + silc_cipher_free(conn->receive_key); + if (conn->hmac) + silc_hmac_free(conn->hmac); + if (conn->hmac_key) { + memset(conn->hmac_key, 0, conn->hmac_key_len); + silc_free(conn->hmac_key); + } + + conn->sock = NULL; + conn->remote_port = 0; + conn->remote_type = 0; + conn->send_key = NULL; + conn->receive_key = NULL; + conn->hmac = NULL; + conn->hmac_key = NULL; + conn->hmac_key_len = 0; + conn->local_id = NULL; + conn->local_id_data = NULL; + conn->remote_host = NULL; + conn->current_channel = NULL; + + silc_client_del_connection(client, conn); + } + + if (sock->protocol) { + silc_protocol_free(sock->protocol); + sock->protocol = NULL; + } + silc_socket_free(sock); +} + +/* Called when we receive disconnection packet from server. This + closes our end properly and displays the reason of the disconnection + on the screen. */ + +void silc_client_disconnected_by_server(SilcClient client, + SilcSocketConnection sock, + SilcBuffer message) +{ + char *msg; + + SILC_LOG_DEBUG(("Server disconnected us, sock %d", sock->sock)); + + msg = silc_calloc(message->len + 1, sizeof(char)); + memcpy(msg, message->data, message->len); + client->ops->say(client, sock->user_data, msg); + silc_free(msg); + + SILC_SET_DISCONNECTED(sock); + silc_client_close_connection(client, sock); +} + +/* Received error message from server. Display it on the screen. + We don't take any action what so ever of the error message. */ + +void silc_client_error_by_server(SilcClient client, + SilcSocketConnection sock, + SilcBuffer message) +{ + char *msg; + + msg = silc_calloc(message->len + 1, sizeof(char)); + memcpy(msg, message->data, message->len); + client->ops->say(client, sock->user_data, msg); + silc_free(msg); +} + +/* Called when notify is received and some async operation (such as command) + is required before processing the notify message. This calls again the + silc_client_notify_by_server and reprocesses the original notify packet. */ + +static void silc_client_notify_by_server_pending(void *context) +{ + SilcPacketContext *p = (SilcPacketContext *)context; + silc_client_notify_by_server(p->context, p->sock, p); + if (p->src_id) + silc_free(p->src_id); + if (p->dst_id) + silc_free(p->dst_id); + silc_buffer_free(p->buffer); + silc_free(p); +} + +/* Received notify message from server */ + +void silc_client_notify_by_server(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcNotifyPayload payload; + SilcNotifyType type; + SilcArgumentPayload args; + int i; + + SilcClientID *client_id = NULL; + SilcChannelID *channel_id = NULL; + SilcClientEntry client_entry; + SilcClientEntry client_entry2; + SilcChannelEntry channel; + SilcIDCacheEntry id_cache = NULL; + unsigned char *tmp; + unsigned int tmp_len, mode; + + payload = silc_notify_payload_parse(buffer); + type = silc_notify_get_type(payload); + args = silc_notify_get_args(payload); + if (!args) + goto out; + + switch(type) { + case SILC_NOTIFY_TYPE_NONE: + /* Notify application */ + client->ops->notify(client, conn, type, + silc_argument_get_arg_type(args, 1, NULL)); + break; + + case SILC_NOTIFY_TYPE_INVITE: + /* + * Someone invited me to a channel. Find Client and Channel entries + * for the application. + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry and if not found query it */ + client_entry = silc_idlist_get_client_by_id(client, conn, client_id, TRUE); + if (!client_entry) { + SilcPacketContext *p = silc_packet_context_dup(packet); + p->context = (void *)client; + p->sock = sock; + silc_client_command_pending(SILC_COMMAND_WHOIS, + silc_client_notify_by_server_pending, p); + goto out; + } + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* XXX Will ALWAYS fail because currently we don't have way to resolve + channel information for channel that we're not joined to. */ + /* XXX ways to fix: use (extended) LIST command, or define the channel + name to the notfy type when name resolving is not mandatory. */ + /* Find channel entry */ + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) + goto out; + + channel = (SilcChannelEntry)id_cache->context; + + /* Notify application */ + client->ops->notify(client, conn, type, client_entry, channel); + break; + + case SILC_NOTIFY_TYPE_JOIN: + /* + * Someone has joined to a channel. Get their ID and nickname and + * cache them for later use. + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry and if not found query it */ + client_entry = silc_idlist_get_client_by_id(client, conn, client_id, TRUE); + if (!client_entry) { + SilcPacketContext *p = silc_packet_context_dup(packet); + p->context = (void *)client; + p->sock = sock; + silc_client_command_pending(SILC_COMMAND_WHOIS, + silc_client_notify_by_server_pending, p); + goto out; + } + + /* Get channel entry */ + channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + + /* Add client to channel */ + for (i = 0; i < channel->clients_count; i++) { + if (channel->clients[i].client == NULL) { + channel->clients[channel->clients_count].client = client_entry; + channel->clients_count++; + break; + } + } + + if (i == channel->clients_count) { + channel->clients = silc_realloc(channel->clients, + sizeof(*channel->clients) * + (channel->clients_count + 1)); + channel->clients[channel->clients_count].client = client_entry; + channel->clients[channel->clients_count].mode = 0; + channel->clients_count++; + } + + /* XXX add support for multiple same nicks on same channel. Check + for them here */ + + /* Notify application. The channel entry is sent last as this notify + is for channel but application don't know it from the arguments + sent by server. */ + client->ops->notify(client, conn, type, client_entry, channel); + break; + + case SILC_NOTIFY_TYPE_LEAVE: + /* + * Someone has left a channel. We will remove it from the channel but + * we'll keep it in the cache in case we'll need it later. + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry */ + client_entry = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry) + goto out; + + /* Get channel entry */ + channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + + /* Remove client from channel */ + for (i = 0; i < channel->clients_count; i++) { + if (channel->clients[i].client == client_entry) { + channel->clients[i].client = NULL; + channel->clients_count--; + break; + } + } + + /* Notify application. The channel entry is sent last as this notify + is for channel but application don't know it from the arguments + sent by server. */ + client->ops->notify(client, conn, type, client_entry, channel); + break; + + case SILC_NOTIFY_TYPE_SIGNOFF: + /* + * Someone left SILC. We'll remove it from all channels and from cache. + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry */ + client_entry = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry) + goto out; + + /* Remove from all channels */ + silc_client_remove_from_channels(client, conn, client_entry); + + /* Remove from cache */ + silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, + client_entry->id); + + /* Notify application */ + client->ops->notify(client, conn, type, client_entry); + + /* Free data */ + if (client_entry->nickname) + silc_free(client_entry->nickname); + if (client_entry->server) + silc_free(client_entry->server); + if (client_entry->id) + silc_free(client_entry->id); + if (client_entry->send_key) + silc_cipher_free(client_entry->send_key); + if (client_entry->receive_key) + silc_cipher_free(client_entry->receive_key); + break; + + case SILC_NOTIFY_TYPE_TOPIC_SET: + /* + * Someone set the topic on a channel. + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry */ + client_entry = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry) + goto out; + + /* Get topic */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + + /* Get channel entry */ + channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + + /* Notify application. The channel entry is sent last as this notify + is for channel but application don't know it from the arguments + sent by server. */ + client->ops->notify(client, conn, type, client_entry, tmp, channel); + break; + + case SILC_NOTIFY_TYPE_NICK_CHANGE: + /* + * Someone changed their nickname. If we don't have entry for the new + * ID we will query it and return here after it's done. After we've + * returned we fetch the old entry and free it and notify the + * application. + */ + + /* Get new Client ID */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Ignore my ID */ + if (!SILC_ID_CLIENT_COMPARE(client_id, conn->local_id)) + break; + + /* Find Client entry and if not found query it */ + client_entry2 = + silc_idlist_get_client_by_id(client, conn, client_id, TRUE); + if (!client_entry2) { + SilcPacketContext *p = silc_packet_context_dup(packet); + p->context = (void *)client; + p->sock = sock; + silc_client_command_pending(SILC_COMMAND_WHOIS, + silc_client_notify_by_server_pending, p); + goto out; + } + + /* Get old Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find old Client entry */ + client_entry = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry) + goto out; + + /* Remove the old from cache */ + silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, + client_entry->id); + + /* Replace old ID entry with new one on all channels. */ + silc_client_replace_from_channels(client, conn, client_entry, + client_entry2); + + /* Notify application */ + client->ops->notify(client, conn, type, client_entry, client_entry2); + + /* Free data */ + if (client_entry->nickname) + silc_free(client_entry->nickname); + if (client_entry->server) + silc_free(client_entry->server); + if (client_entry->id) + silc_free(client_entry->id); + if (client_entry->send_key) + silc_cipher_free(client_entry->send_key); + if (client_entry->receive_key) + silc_cipher_free(client_entry->receive_key); + break; + + case SILC_NOTIFY_TYPE_CMODE_CHANGE: + /* + * Someone changed a channel mode + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry */ + client_entry = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry) + goto out; + + /* Get the mode */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + + SILC_GET32_MSB(mode, tmp); + + /* Get channel entry */ + channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + + /* Save the new mode */ + channel->mode = mode; + + /* Notify application. The channel entry is sent last as this notify + is for channel but application don't know it from the arguments + sent by server. */ + client->ops->notify(client, conn, type, client_entry, mode, channel); + break; + + case SILC_NOTIFY_TYPE_CUMODE_CHANGE: + /* + * Someone changed user's mode on a channel + */ + + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find Client entry */ + client_entry = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry) + goto out; + + /* Get the mode */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + + SILC_GET32_MSB(mode, tmp); + + /* Get target Client ID */ + tmp = silc_argument_get_arg_type(args, 3, &tmp_len); + if (!tmp) + goto out; + + silc_free(client_id); + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Find target Client entry */ + client_entry2 = + silc_idlist_get_client_by_id(client, conn, client_id, FALSE); + if (!client_entry2) + goto out; + + /* Get channel entry */ + channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + + /* Save the mode */ + for (i = 0; i < channel->clients_count; i++) { + if (channel->clients[i].client == client_entry2) { + channel->clients[i].mode = mode; + break; + } + } + + /* Notify application. The channel entry is sent last as this notify + is for channel but application don't know it from the arguments + sent by server. */ + client->ops->notify(client, conn, type, client_entry, mode, + client_entry2, channel); + break; + + case SILC_NOTIFY_TYPE_MOTD: + /* + * Received Message of the day + */ + + /* Get motd */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + + /* Notify application */ + client->ops->notify(client, conn, type, tmp); + break; + + default: + break; + } + + out: + silc_notify_payload_free(payload); + if (client_id) + silc_free(client_id); + if (channel_id) + silc_free(channel_id); +} + +/* Processes the received new Client ID from server. Old Client ID is + deleted from cache and new one is added. */ + +void silc_client_receive_new_id(SilcClient client, + SilcSocketConnection sock, + SilcIDPayload idp) +{ + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + + /* Delete old ID from ID cache */ + silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, conn->local_id); + + /* Save the new ID */ + if (conn->local_id) + silc_free(conn->local_id); + if (conn->local_id_data) + silc_free(conn->local_id_data); + + conn->local_id = silc_id_payload_get_id(idp); + conn->local_id_data = silc_id_payload_get_data(idp); + conn->local_id_data_len = silc_id_payload_get_len(idp);; + + if (!conn->local_entry) + conn->local_entry = silc_calloc(1, sizeof(*conn->local_entry)); + + conn->local_entry->nickname = conn->nickname; + if (!conn->local_entry->username) { + conn->local_entry->username = + silc_calloc(strlen(client->username) + strlen(client->hostname) + 1, + sizeof(conn->local_entry->username)); + sprintf(conn->local_entry->username, "%s@%s", client->username, + client->hostname); + } + conn->local_entry->id = conn->local_id; + + /* Put it to the ID cache */ + silc_idcache_add(conn->client_cache, conn->nickname, SILC_ID_CLIENT, + conn->local_id, (void *)conn->local_entry, TRUE); +} + +/* Processed received Channel ID for a channel. This is called when client + joins to channel and server replies with channel ID. The ID is cached. */ + +void silc_client_new_channel_id(SilcClient client, + SilcSocketConnection sock, + char *channel_name, + unsigned int mode, SilcIDPayload idp) +{ + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcChannelEntry channel; + + SILC_LOG_DEBUG(("New channel ID")); + + channel = silc_calloc(1, sizeof(*channel)); + channel->channel_name = channel_name; + channel->id = silc_id_payload_get_id(idp); + channel->mode = mode; + conn->current_channel = channel; + + /* Put it to the ID cache */ + silc_idcache_add(conn->channel_cache, channel_name, SILC_ID_CHANNEL, + (void *)channel->id, (void *)channel, TRUE); +} + +/* Processes received key for channel. The received key will be used + to protect the traffic on the channel for now on. Client must receive + the key to the channel before talking on the channel is possible. + This is the key that server has generated, this is not the channel + private key, it is entirely local setting. */ + +void silc_client_receive_channel_key(SilcClient client, + SilcSocketConnection sock, + SilcBuffer packet) +{ + unsigned char *id_string, *key, *cipher; + unsigned int tmp_len; + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcChannelID *id; + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry channel; + SilcChannelKeyPayload payload; + + SILC_LOG_DEBUG(("Received key for channel")); + + payload = silc_channel_key_payload_parse(packet); + if (!payload) + return; + + id_string = silc_channel_key_get_id(payload, &tmp_len); + if (!id_string) { + silc_channel_key_payload_free(payload); + return; + } + id = silc_id_payload_parse_id(id_string, tmp_len); + + /* Find channel. */ + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id, + SILC_ID_CHANNEL, &id_cache)) + goto out; + + /* Save the key */ + key = silc_channel_key_get_key(payload, &tmp_len); + cipher = silc_channel_key_get_cipher(payload, NULL); + + channel = (SilcChannelEntry)id_cache->context; + channel->key_len = tmp_len; + channel->key = silc_calloc(tmp_len, sizeof(*channel->key)); + memcpy(channel->key, key, tmp_len); + + if (!silc_cipher_alloc(cipher, &channel->channel_key)) { + client->ops->say(client, conn, + "Cannot talk to channel: unsupported cipher %s", cipher); + goto out; + } + channel->channel_key->cipher->set_key(channel->channel_key->context, + key, tmp_len); + + /* Client is now joined to the channel */ + channel->on_channel = TRUE; + + out: + silc_free(id); + silc_channel_key_payload_free(payload); +} + +/* Process received message to a channel (or from a channel, really). This + decrypts the channel message with channel specific key and parses the + channel payload. Finally it displays the message on the screen. */ + +void silc_client_channel_message(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcBuffer buffer = packet->buffer; + SilcChannelPayload payload = NULL; + SilcChannelID *id = NULL; + SilcChannelEntry channel; + SilcIDCacheEntry id_cache = NULL; + SilcClientID *client_id = NULL; + int i; + char *nickname; + + /* Sanity checks */ + if (packet->dst_id_type != SILC_ID_CHANNEL) + goto out; + + client_id = silc_id_str2id(packet->src_id, SILC_ID_CLIENT); + id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); + + /* Find the channel entry from channels on this connection */ + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)id, + SILC_ID_CHANNEL, &id_cache)) + goto out; + + channel = (SilcChannelEntry)id_cache->context; + + /* Decrypt the channel message payload. Push the IV out of the way, + since it is not encrypted (after pushing buffer->tail has the IV). */ + silc_buffer_push_tail(buffer, 16); + channel->channel_key->cipher->decrypt(channel->channel_key->context, + buffer->data, buffer->data, + buffer->len, buffer->tail); + silc_buffer_pull_tail(buffer, 16); + + /* Parse the channel message payload */ + payload = silc_channel_payload_parse(buffer); + if (!payload) + goto out; + + /* Find nickname */ + nickname = "[unknown]"; + for (i = 0; i < channel->clients_count; i++) { + if (channel->clients[i].client && + !SILC_ID_CLIENT_COMPARE(channel->clients[i].client->id, client_id)) + nickname = channel->clients[i].client->nickname; + } + + /* Pass the message to application */ + client->ops->channel_message(client, conn, nickname, + channel->channel_name, + silc_channel_get_data(payload, NULL)); + + out: + if (id) + silc_free(id); + if (client_id) + silc_free(client_id); + if (payload) + silc_channel_payload_free(payload); +} + +/* Private message received. This processes the private message and + finally displays it on the screen. */ + +void silc_client_private_message(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcClientConnection conn = (SilcClientConnection)sock->user_data; + SilcBuffer buffer = packet->buffer; + unsigned short nick_len; + unsigned char *nickname, *message; + + /* Get nickname */ + silc_buffer_unformat(buffer, + SILC_STR_UI16_NSTRING_ALLOC(&nickname, &nick_len), + SILC_STR_END); + silc_buffer_pull(buffer, 2 + nick_len); + + message = silc_calloc(buffer->len + 1, sizeof(char)); + memcpy(message, buffer->data, buffer->len); + + /* Pass the private message to application */ + client->ops->private_message(client, conn, nickname, message); + + /* See if we are away (gone). If we are away we will reply to the + sender with the set away message. */ + if (conn->away && conn->away->away) { + SilcClientID *remote_id; + SilcClientEntry remote_client; + SilcIDCacheEntry id_cache; + + if (packet->src_id_type != SILC_ID_CLIENT) + goto out; + + remote_id = silc_id_str2id(packet->src_id, SILC_ID_CLIENT); + if (!remote_id) + goto out; + + /* If it's me, ignore */ + if (!SILC_ID_CLIENT_COMPARE(remote_id, conn->local_id)) + goto out; + + /* Check whether we know this client already */ + if (!silc_idcache_find_by_id_one(conn->client_cache, remote_id, + SILC_ID_CLIENT, &id_cache)) + { + /* Allocate client entry */ + remote_client = silc_calloc(1, sizeof(*remote_client)); + remote_client->id = remote_id; + silc_parse_nickname(nickname, &remote_client->nickname, + &remote_client->server, &remote_client->num); + + /* Save the client to cache */ + silc_idcache_add(conn->client_cache, remote_client->nickname, + SILC_ID_CLIENT, remote_client->id, remote_client, + TRUE); + } else { + silc_free(remote_id); + remote_client = (SilcClientEntry)id_cache->context; + } + + /* Send the away message */ + silc_client_packet_send_private_message(client, sock, remote_client, + conn->away->away, + strlen(conn->away->away), TRUE); + } + + out: + memset(message, 0, buffer->len); + silc_free(message); + silc_free(nickname); +} + +/* Removes a client entry from all channel it has joined. This really is + a performance killer (client_entry should have pointers to channel + entry list). */ + +void silc_client_remove_from_channels(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry) +{ + SilcIDCacheEntry id_cache; + SilcIDCacheList list; + SilcChannelEntry channel; + int i; + + if (!silc_idcache_find_by_id(conn->channel_cache, SILC_ID_CACHE_ANY, + SILC_ID_CHANNEL, &list)) + return; + + silc_idcache_list_first(list, &id_cache); + channel = (SilcChannelEntry)id_cache->context; + + while (channel) { + + /* Remove client from channel */ + for (i = 0; i < channel->clients_count; i++) { + if (channel->clients[i].client == client_entry) { + channel->clients[i].client = NULL; + channel->clients_count--; + break; + } + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + } + + silc_idcache_list_free(list); +} + +/* Replaces `old' client entries from all channels to `new' client entry. + This can be called for example when nickname changes and old ID entry + is replaced from ID cache with the new one. If the old ID entry is only + updated, then this fucntion needs not to be called. */ + +void silc_client_replace_from_channels(SilcClient client, + SilcClientConnection conn, + SilcClientEntry old, + SilcClientEntry new) +{ + SilcIDCacheEntry id_cache; + SilcIDCacheList list; + SilcChannelEntry channel; + int i; + + if (!silc_idcache_find_by_id(conn->channel_cache, SILC_ID_CACHE_ANY, + SILC_ID_CHANNEL, &list)) + return; + + silc_idcache_list_first(list, &id_cache); + channel = (SilcChannelEntry)id_cache->context; + + while (channel) { + + /* Remove client from channel */ + for (i = 0; i < channel->clients_count; i++) { + if (channel->clients[i].client == old) { + channel->clients[i].client = new; + break; + } + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + + channel = (SilcChannelEntry)id_cache->context; + } + + silc_idcache_list_free(list); +} ++ ++/* Parses mode mask and returns the mode as string. */ ++ ++char *silc_client_chmode(unsigned int mode) ++{ ++ char string[20]; ++ ++ if (!mode) ++ return NULL; ++ ++ memset(string, 0, sizeof(string)); ++ ++ if (mode & SILC_CHANNEL_MODE_PRIVATE) ++ strncat(string, "p", 1); ++ ++ if (mode & SILC_CHANNEL_MODE_SECRET) ++ strncat(string, "s", 1); ++ ++ if (mode & SILC_CHANNEL_MODE_PRIVKEY) ++ strncat(string, "k", 1); ++ ++ if (mode & SILC_CHANNEL_MODE_INVITE) ++ strncat(string, "i", 1); ++ ++ if (mode & SILC_CHANNEL_MODE_TOPIC) ++ strncat(string, "t", 1); ++ ++ if (mode & SILC_CHANNEL_MODE_ULIMIT) ++ strncat(string, "l", 1); ++ ++ if (mode & SILC_CHANNEL_MODE_PASSPHRASE) ++ strncat(string, "a", 1); ++ ++ /* Rest of mode is ignored */ ++ ++ return strdup(string); ++} ++ ++/* Parses channel user mode mask and returns te mode as string */ ++ ++char *silc_client_chumode(unsigned int mode) ++{ ++ char string[4]; ++ ++ if (!mode) ++ return NULL; ++ ++ memset(string, 0, sizeof(string)); ++ ++ if (mode & SILC_CHANNEL_UMODE_CHANFO) ++ strncat(string, "f", 1); ++ ++ if (mode & SILC_CHANNEL_UMODE_CHANOP) ++ strncat(string, "o", 1); ++ ++ return strdup(string); ++} ++ ++/* Parses channel user mode and returns it as special mode character. */ ++ ++char *silc_client_chumode_char(unsigned int mode) ++{ ++ char string[4]; ++ ++ if (!mode) ++ return NULL; ++ ++ memset(string, 0, sizeof(string)); ++ ++ if (mode & SILC_CHANNEL_UMODE_CHANFO) ++ strncat(string, "*", 1); ++ ++ if (mode & SILC_CHANNEL_UMODE_CHANOP) ++ strncat(string, "@", 1); ++ ++ return strdup(string); ++} diff --cc lib/silcclient/client.h index 84cfa324,661bd83a..fdff610e --- a/lib/silcclient/client.h +++ b/lib/silcclient/client.h @@@ -278,14 -246,4 +278,18 @@@ void silc_client_receive_channel_key(Si void silc_client_channel_message(SilcClient client, SilcSocketConnection sock, SilcPacketContext *packet); +void silc_client_private_message(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_client_remove_from_channels(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry); +void silc_client_replace_from_channels(SilcClient client, + SilcClientConnection conn, + SilcClientEntry old, + SilcClientEntry new); ++char *silc_client_chmode(unsigned int mode); ++char *silc_client_chumode(unsigned int mode); ++char *silc_client_chumode_char(unsigned int mode); ++ #endif diff --cc lib/silcclient/command_reply.c index 5ed0a3b1,00000000..8b2f0c35 mode 100644,000000..100644 --- a/lib/silcclient/command_reply.c +++ b/lib/silcclient/command_reply.c @@@ -1,1136 -1,0 +1,1145 @@@ +/* + + command_reply.c + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2000 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. + +*/ +/* + * Command reply functions are "the otherside" of the command functions. + * Reply to a command sent by server is handled by these functions. + * + * The arguments received from server are also passed to the calling + * application through command_reply client operation. The arguments are + * exactly same and in same order as the server sent it. However, ID's are + * not sent to the application. Instead, corresponding ID entry is sent + * to the application. For example, instead of sending Client ID the + * corresponding SilcClientEntry is sent to the application. The case is + * same with for example Channel ID's. This way application has all the + * necessary data already in hand without redundant searching. If ID is + * received but ID entry does not exist, NULL is sent. + */ +/* $Id$ */ + +#include "clientlibincludes.h" + +/* Client command reply list. */ +SilcClientCommandReply silc_command_reply_list[] = +{ + SILC_CLIENT_CMD_REPLY(whois, WHOIS), + SILC_CLIENT_CMD_REPLY(whowas, WHOWAS), + SILC_CLIENT_CMD_REPLY(identify, IDENTIFY), + SILC_CLIENT_CMD_REPLY(nick, NICK), + SILC_CLIENT_CMD_REPLY(list, LIST), + SILC_CLIENT_CMD_REPLY(topic, TOPIC), + SILC_CLIENT_CMD_REPLY(invite, INVITE), + SILC_CLIENT_CMD_REPLY(quit, QUIT), + SILC_CLIENT_CMD_REPLY(kill, KILL), + SILC_CLIENT_CMD_REPLY(info, INFO), + SILC_CLIENT_CMD_REPLY(connect, CONNECT), + SILC_CLIENT_CMD_REPLY(ping, PING), + SILC_CLIENT_CMD_REPLY(oper, OPER), + SILC_CLIENT_CMD_REPLY(join, JOIN), + SILC_CLIENT_CMD_REPLY(motd, MOTD), + SILC_CLIENT_CMD_REPLY(umode, UMODE), + SILC_CLIENT_CMD_REPLY(cmode, CMODE), + SILC_CLIENT_CMD_REPLY(cumode, CUMODE), + SILC_CLIENT_CMD_REPLY(kick, KICK), + SILC_CLIENT_CMD_REPLY(restart, RESTART), + SILC_CLIENT_CMD_REPLY(close, CLOSE), + SILC_CLIENT_CMD_REPLY(die, DIE), + SILC_CLIENT_CMD_REPLY(silcoper, SILCOPER), + SILC_CLIENT_CMD_REPLY(leave, LEAVE), + SILC_CLIENT_CMD_REPLY(names, NAMES), + + { NULL, 0 }, +}; + +/* Status message structure. Messages are defined below. */ +typedef struct { + SilcCommandStatus status; + char *message; +} SilcCommandStatusMessage; + +/* Status messages returned by the server */ +#define STAT(x) SILC_STATUS_ERR_##x +const SilcCommandStatusMessage silc_command_status_messages[] = { + + { STAT(NO_SUCH_NICK), "No such nickname" }, + { STAT(NO_SUCH_CHANNEL), "No such channel" }, + { STAT(NO_SUCH_SERVER), "No such server" }, + { STAT(TOO_MANY_TARGETS), "Duplicate recipients. No message delivered" }, + { STAT(NO_RECIPIENT), "No recipient given" }, + { STAT(UNKNOWN_COMMAND), "Unknown command" }, + { STAT(WILDCARDS), "Unknown command" }, + { STAT(NO_CLIENT_ID), "No Client ID given" }, + { STAT(NO_CHANNEL_ID), "No Channel ID given" }, + { STAT(NO_SERVER_ID), "No Server ID given" }, + { STAT(BAD_CLIENT_ID), "Bad Client ID" }, + { STAT(BAD_CHANNEL_ID), "Bad Channel ID" }, + { STAT(NO_SUCH_CLIENT_ID), "No such Client ID" }, + { STAT(NO_SUCH_CHANNEL_ID),"No such Channel ID" }, + { STAT(NICKNAME_IN_USE), "Nickname already exists" }, + { STAT(NOT_ON_CHANNEL), "You are not on that channel" }, + { STAT(USER_NOT_ON_CHANNEL),"They are not on the channel" }, + { STAT(USER_ON_CHANNEL), "User already on the channel" }, + { STAT(NOT_REGISTERED), "You have not registered" }, + { STAT(NOT_ENOUGH_PARAMS), "Not enough parameters" }, + { STAT(TOO_MANY_PARAMS), "Too many parameters" }, + { STAT(PERM_DENIED), "Your host is not among the privileged" }, + { STAT(BANNED_FROM_SERVER),"You are banned from this server" }, + { STAT(BAD_PASSWORD), "Cannot join channel. Incorrect password" }, + { STAT(CHANNEL_IS_FULL), "Cannot join channel. Channel is full" }, + { STAT(NOT_INVITED), "Cannot join channel. You have not been invited" }, + { STAT(BANNED_FROM_CHANNEL), "Cannot join channel. You have been banned" }, + { STAT(UNKNOWN_MODE), "Unknown mode" }, + { STAT(NOT_YOU), "Cannot change mode for other users" }, + { STAT(NO_CHANNEL_PRIV), "Permission denied. You are not channel operator" }, + { STAT(NO_SERVER_PRIV), "Permission denied. You are not server operator" }, + { STAT(NO_ROUTER_PRIV), "Permission denied. You are not SILC operator" }, + { STAT(BAD_NICKNAME), "Bad nickname" }, + { STAT(BAD_CHANNEL), "Bad channel name" }, + { STAT(AUTH_FAILED), "Authentication failed" }, + + { 0, NULL } +}; + +/* Command reply operation that is called at the end of all command replys. + Usage: COMMAND_REPLY((ARGS, argument1, argument2, etc...)), */ +#define COMMAND_REPLY(args) cmd->client->ops->command_reply args +#define ARGS cmd->client, cmd->sock->user_data, \ + cmd->payload, TRUE, silc_command_get(cmd->payload), status + +/* Error reply to application. Usage: COMMAND_REPLY_ERROR; */ +#define COMMAND_REPLY_ERROR cmd->client->ops->command_reply(cmd->client, \ + cmd->sock->user_data, cmd->payload, FALSE, \ + silc_command_get(cmd->payload), status) + +/* Process received command reply. */ + +void silc_client_command_reply_process(SilcClient client, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcClientCommandReplyContext ctx; + SilcCommandPayload payload; + + /* Get command reply payload from packet */ + payload = silc_command_payload_parse(buffer); + if (!payload) { + /* Silently ignore bad reply packet */ + SILC_LOG_DEBUG(("Bad command reply packet")); + return; + } + + /* Allocate command reply context. This must be free'd by the + command reply routine receiving it. */ + ctx = silc_calloc(1, sizeof(*ctx)); + ctx->client = client; + ctx->sock = sock; + ctx->payload = payload; + ctx->args = silc_command_get_args(ctx->payload); + ctx->packet = packet; + + /* Check for pending commands and mark to be exeucted */ + SILC_CLIENT_COMMAND_CHECK_PENDING(ctx); + + /* Execute command reply */ + SILC_CLIENT_COMMAND_REPLY_EXEC(ctx); +} + +/* Returns status message string */ + +static char * +silc_client_command_status_message(SilcCommandStatus status) +{ + int i; + + for (i = 0; silc_command_status_messages[i].message; i++) { + if (silc_command_status_messages[i].status == status) + break; + } + + if (silc_command_status_messages[i].message == NULL) + return NULL; + + return silc_command_status_messages[i].message; +} + +/* Free command reply context and its internals. */ + +void silc_client_command_reply_free(SilcClientCommandReplyContext cmd) +{ + if (cmd) { + silc_command_free_payload(cmd->payload); + silc_free(cmd); + } +} + +static void +silc_client_command_reply_whois_print(SilcClientCommandReplyContext cmd, + SilcCommandStatus status) +{ + char buf[256]; + int argc, len; + unsigned char *id_data; + char *nickname = NULL, *username = NULL; + char *realname = NULL; + SilcClientID *client_id; + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client_entry = NULL; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + + memset(buf, 0, sizeof(buf)); + + argc = silc_argument_get_arg_num(cmd->args); + + id_data = silc_argument_get_arg_type(cmd->args, 2, &len); + if (!id_data) { + COMMAND_REPLY_ERROR; + return; + } + + client_id = silc_id_payload_parse_id(id_data, len); + + nickname = silc_argument_get_arg_type(cmd->args, 3, &len); + if (nickname) { + strncat(buf, nickname, len); + strncat(buf, " is ", 4); + } + + username = silc_argument_get_arg_type(cmd->args, 4, &len); + if (username) { + strncat(buf, username, len); + } + + realname = silc_argument_get_arg_type(cmd->args, 5, &len); + if (realname) { + strncat(buf, " (", 2); + strncat(buf, realname, len); + strncat(buf, ")", 1); + } + + /* Check if we have this client cached already. */ + if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id, + SILC_ID_CLIENT, &id_cache)) { + client_entry = silc_calloc(1, sizeof(*client_entry)); + client_entry->id = client_id; + silc_parse_nickname(nickname, &client_entry->nickname, + &client_entry->server, &client_entry->num); + client_entry->username = strdup(username); + + /* Add client to cache */ + silc_idcache_add(conn->client_cache, client_entry->nickname, + SILC_ID_CLIENT, client_id, (void *)client_entry, TRUE); + } else { + client_entry = (SilcClientEntry)id_cache->context; + silc_free(client_id); + } + + if (!cmd->callback) + cmd->client->ops->say(cmd->client, conn, "%s", buf); + + /* Notify application */ + COMMAND_REPLY((ARGS, client_entry, nickname, + username, realname, NULL, NULL)); +} + +/* Received reply for WHOIS command. This maybe called several times + for one WHOIS command as server may reply with list of results. */ + +SILC_CLIENT_CMD_REPLY_FUNC(whois) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + unsigned char *tmp; + + SILC_LOG_DEBUG(("Start")); + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK && + status != SILC_STATUS_LIST_START && + status != SILC_STATUS_LIST_ITEM && + status != SILC_STATUS_LIST_END) { + if (status == SILC_STATUS_ERR_NO_SUCH_NICK) { + /* Take nickname which may be provided */ + tmp = silc_argument_get_arg_type(cmd->args, 3, NULL); + if (tmp) + cmd->client->ops->say(cmd->client, conn, "%s: %s", tmp, + silc_client_command_status_message(status)); + else + cmd->client->ops->say(cmd->client, conn, "%s", + silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } else { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + } + + /* Display one whois reply */ + if (status == SILC_STATUS_OK) { + silc_client_command_reply_whois_print(cmd, status); + } + + /* XXX list should not be displayed untill all items has been received. */ + if (status == SILC_STATUS_LIST_START) { + silc_client_command_reply_whois_print(cmd, status); + } + + if (status == SILC_STATUS_LIST_ITEM) { + silc_client_command_reply_whois_print(cmd, status); + } + + if (status == SILC_STATUS_LIST_END) { + silc_client_command_reply_whois_print(cmd, status); + } + + SILC_CLIENT_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_WHOIS); + + out: + silc_client_command_reply_free(cmd); +} + +/* Received reply for WHOWAS command. */ + +SILC_CLIENT_CMD_REPLY_FUNC(whowas) +{ + +} + +/* Received reply for IDENTIFY command. This maybe called several times + for one IDENTIFY command as server may reply with list of results. + This is totally silent and does not print anything on screen. */ + +SILC_CLIENT_CMD_REPLY_FUNC(identify) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcClientEntry client_entry; + SilcCommandStatus status; + unsigned char *tmp; + + SILC_LOG_DEBUG(("Start")); + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK) { + if (status == SILC_STATUS_ERR_NO_SUCH_NICK) { + /* Take nickname which may be provided */ + tmp = silc_argument_get_arg_type(cmd->args, 3, NULL); + if (tmp) + cmd->client->ops->say(cmd->client, conn, "%s: %s", tmp, + silc_client_command_status_message(status)); + else + cmd->client->ops->say(cmd->client, conn, "%s", + silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } else { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + } + + /* Display one whois reply */ + if (status == SILC_STATUS_OK) { + unsigned int len; + unsigned char *id_data; + char *nickname; + char *username; + + id_data = silc_argument_get_arg_type(cmd->args, 2, &len); + nickname = silc_argument_get_arg_type(cmd->args, 3, NULL); + username = silc_argument_get_arg_type(cmd->args, 4, NULL); + + /* Allocate client entry */ + client_entry = silc_calloc(1, sizeof(*client_entry)); + client_entry->id = silc_id_payload_parse_id(id_data, len); + if (nickname) + silc_parse_nickname(nickname, &client_entry->nickname, + &client_entry->server, &client_entry->num); + if (username) + client_entry->username = strdup(username); + + /* Save received Client ID to ID cache */ + silc_idcache_add(conn->client_cache, client_entry->nickname, + SILC_ID_CLIENT, client_entry->id, client_entry, TRUE); + } + + if (status == SILC_STATUS_LIST_START) { + + } + + if (status == SILC_STATUS_LIST_END) { + + } + + SILC_CLIENT_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_IDENTIFY); + + out: + silc_client_command_reply_free(cmd); +} + +/* Received reply for command NICK. If everything went without errors + we just received our new Client ID. */ + +SILC_CLIENT_CMD_REPLY_FUNC(nick) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + SilcIDPayload idp; + unsigned char *tmp; + unsigned int argc, len; + + SILC_LOG_DEBUG(("Start")); + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, "Cannot set nickname: %s", + silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + + argc = silc_argument_get_arg_num(cmd->args); + if (argc < 2 || argc > 2) { + cmd->client->ops->say(cmd->client, conn, + "Cannot set nickname: bad reply to command"); + COMMAND_REPLY_ERROR; + goto out; + } + + /* Take received Client ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &len); + idp = silc_id_payload_parse_data(tmp, len); + silc_client_receive_new_id(cmd->client, cmd->sock, idp); + + /* Notify application */ + COMMAND_REPLY((ARGS, conn->local_entry)); + + out: + silc_client_command_reply_free(cmd); +} + +SILC_CLIENT_CMD_REPLY_FUNC(list) +{ +} + +/* Received reply to topic command. */ + +SILC_CLIENT_CMD_REPLY_FUNC(topic) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + SilcChannelEntry channel; + SilcChannelID *channel_id = NULL; + SilcIDCacheEntry id_cache = NULL; + unsigned char *tmp; + char *topic; + unsigned int argc, len; + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + silc_client_command_reply_free(cmd); + return; + } + + argc = silc_argument_get_arg_num(cmd->args); + if (argc < 1 || argc > 3) { + COMMAND_REPLY_ERROR; + goto out; + } + + /* Take Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &len); + if (!tmp) + goto out; + + /* Take topic */ + topic = silc_argument_get_arg_type(cmd->args, 3, NULL); + if (!topic) + goto out; + + channel_id = silc_id_payload_parse_id(tmp, len); + + /* Get the channel name */ + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) { + silc_free(channel_id); + COMMAND_REPLY_ERROR; + goto out; + } + + channel = (SilcChannelEntry)id_cache->context; + + cmd->client->ops->say(cmd->client, conn, + "Topic on channel %s: %s", channel->channel_name, + topic); + + /* Notify application */ + COMMAND_REPLY((ARGS, channel, topic)); + + out: + silc_client_command_reply_free(cmd); +} + +/* Received reply to invite command. */ + +SILC_CLIENT_CMD_REPLY_FUNC(invite) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + unsigned char *tmp; + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + silc_client_command_reply_free(cmd); + return; + } + + /* Notify application */ + COMMAND_REPLY((ARGS)); + + silc_client_command_reply_free(cmd); +} + +SILC_CLIENT_CMD_REPLY_FUNC(quit) +{ +} + +SILC_CLIENT_CMD_REPLY_FUNC(kill) +{ +} + +/* Received reply to INFO command. We receive the server ID and some + information about the server user requested. */ + +SILC_CLIENT_CMD_REPLY_FUNC(info) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcClient client = cmd->client; + SilcCommandStatus status; + unsigned char *tmp; + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + silc_client_command_reply_free(cmd); + return; + } + + /* Get server ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp) + goto out; + + /* XXX save server id */ + + /* Get server info */ + tmp = silc_argument_get_arg_type(cmd->args, 3, NULL); + if (!tmp) + goto out; + + client->ops->say(cmd->client, conn, "Info: %s", tmp); + + /* Notify application */ + COMMAND_REPLY((ARGS, NULL, (char *)tmp)); + + out: + silc_client_command_reply_free(cmd); +} + +SILC_CLIENT_CMD_REPLY_FUNC(connect) +{ +} + +/* Received reply to PING command. The reply time is shown to user. */ + +SILC_CLIENT_CMD_REPLY_FUNC(ping) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + void *id; + int i; + time_t diff, curtime; + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + + curtime = time(NULL); + id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type); + + for (i = 0; i < conn->ping_count; i++) { + if (!SILC_ID_SERVER_COMPARE(conn->ping[i].dest_id, id)) { + diff = curtime - conn->ping[i].start_time; + cmd->client->ops->say(cmd->client, conn, + "Ping reply from %s: %d second%s", + conn->ping[i].dest_name, diff, + diff == 1 ? "" : "s"); + + conn->ping[i].start_time = 0; + silc_free(conn->ping[i].dest_id); + conn->ping[i].dest_id = NULL; + silc_free(conn->ping[i].dest_name); + conn->ping[i].dest_name = NULL; + + /* Notify application */ + COMMAND_REPLY((ARGS)); + break; + } + } + + silc_free(id); + + out: + silc_client_command_reply_free(cmd); +} + +SILC_CLIENT_CMD_REPLY_FUNC(oper) +{ +} + +/* Received reply for JOIN command. */ + +SILC_CLIENT_CMD_REPLY_FUNC(join) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcClient client = cmd->client; + SilcCommandStatus status; + SilcIDPayload idp; + unsigned int argc, mode, len; + char *topic, *tmp, *channel_name; + + SILC_LOG_DEBUG(("Start")); + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + + argc = silc_argument_get_arg_num(cmd->args); + if (argc < 3 || argc > 5) { + cmd->client->ops->say(cmd->client, conn, + "Cannot join channel: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + + /* Get channel name */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp) { + cmd->client->ops->say(cmd->client, conn, + "Cannot join channel: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + channel_name = strdup(tmp); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 3, &len); + if (!tmp) { + cmd->client->ops->say(cmd->client, conn, + "Cannot join channel: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + idp = silc_id_payload_parse_data(tmp, len); + + /* Get channel mode */ + tmp = silc_argument_get_arg_type(cmd->args, 4, NULL); + if (tmp) + SILC_GET32_MSB(mode, tmp); + else + mode = 0; + + /* Get topic */ + topic = silc_argument_get_arg_type(cmd->args, 5, NULL); + + /* Save received Channel ID */ + silc_client_new_channel_id(cmd->client, cmd->sock, channel_name, + mode, idp); + silc_id_payload_free(idp); + + if (topic) + client->ops->say(cmd->client, conn, + "Topic for %s: %s", channel_name, topic); + + /* Notify application */ + COMMAND_REPLY((ARGS, channel_name, conn->current_channel, mode, + NULL, NULL, topic)); + + out: + silc_client_command_reply_free(cmd); +} + +/* Received reply for MOTD command */ + +SILC_CLIENT_CMD_REPLY_FUNC(motd) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + unsigned int argc, i; + unsigned char *tmp; + char *motd = NULL, *cp, line[256]; + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + return; + } + + argc = silc_argument_get_arg_num(cmd->args); + if (argc > 2) { + COMMAND_REPLY_ERROR; + goto out; + } + + if (argc == 2) { + motd = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!motd) { + COMMAND_REPLY_ERROR; + goto out; + } + + i = 0; + cp = motd; + while(cp[i] != 0) { + if (cp[i++] == '\n') { + memset(line, 0, sizeof(line)); + strncat(line, cp, i - 1); + cp += i; + + if (i == 2) + line[0] = ' '; + + cmd->client->ops->say(cmd->client, conn, "%s", line); + + if (!strlen(cp)) + break; + i = 0; + } + } + } + + /* Notify application */ + COMMAND_REPLY((ARGS, motd)); + + out: + silc_client_command_reply_free(cmd); +} + +SILC_CLIENT_CMD_REPLY_FUNC(umode) +{ +} + +/* Received reply for CMODE command. */ + +SILC_CLIENT_CMD_REPLY_FUNC(cmode) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + unsigned char *tmp; + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + + /* Get channel mode */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp) { + COMMAND_REPLY_ERROR; + goto out; + } + + /* Notify application */ + COMMAND_REPLY((ARGS, tmp)); + + out: + silc_client_command_reply_free(cmd); +} + +/* Received reply for CUMODE command */ + +SILC_CLIENT_CMD_REPLY_FUNC(cumode) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + SilcIDCacheEntry id_cache = NULL; + SilcClientID *client_id; + unsigned char *tmp, *id; + unsigned int len; + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + + /* Get channel mode */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp) { + COMMAND_REPLY_ERROR; + goto out; + } + + /* Get Client ID */ + id = silc_argument_get_arg_type(cmd->args, 3, &len); + if (!id) { + COMMAND_REPLY_ERROR; + goto out; + } + client_id = silc_id_payload_parse_id(id, len); + + /* Get client entry */ + if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id, + SILC_ID_CLIENT, &id_cache)) { + COMMAND_REPLY_ERROR; + goto out; + } + + /* Notify application */ + COMMAND_REPLY((ARGS, tmp, (SilcClientEntry)id_cache->context)); + silc_free(client_id); + + out: + silc_client_command_reply_free(cmd); +} + +SILC_CLIENT_CMD_REPLY_FUNC(kick) +{ +} + +SILC_CLIENT_CMD_REPLY_FUNC(restart) +{ +} + +SILC_CLIENT_CMD_REPLY_FUNC(close) +{ +} + +SILC_CLIENT_CMD_REPLY_FUNC(die) +{ +} + +SILC_CLIENT_CMD_REPLY_FUNC(silcoper) +{ +} + +/* Reply to LEAVE command. */ + +SILC_CLIENT_CMD_REPLY_FUNC(leave) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + unsigned char *tmp; + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + return; + } + + /* Notify application */ + COMMAND_REPLY((ARGS)); + + silc_client_command_reply_free(cmd); +} + +/* Reply to NAMES command. Received list of client names on the channel + we requested. */ + +SILC_CLIENT_CMD_REPLY_FUNC(names) +{ + SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; + SilcClientConnection conn = (SilcClientConnection)cmd->sock->user_data; + SilcCommandStatus status; + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry channel; + SilcChannelID *channel_id = NULL; + SilcBuffer client_id_list; + SilcBuffer client_mode_list; + unsigned char *tmp; + char *name_list, *cp; + int i, k, len1, len2, list_count = 0; + + SILC_LOG_DEBUG(("Start")); + + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + SILC_GET16_MSB(status, tmp); + if (status != SILC_STATUS_OK) { + cmd->client->ops->say(cmd->client, conn, + "%s", silc_client_command_status_message(status)); + COMMAND_REPLY_ERROR; + goto out; + } + + /* Get channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &len1); + if (!tmp) { + cmd->client->ops->say(cmd->client, conn, + "Cannot Channel ID: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + channel_id = silc_id_payload_parse_id(tmp, len1); + + /* Get the name list of the channel */ + name_list = silc_argument_get_arg_type(cmd->args, 3, &len1); + if (!name_list) { + cmd->client->ops->say(cmd->client, conn, + "Cannot get user list: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + + /* Get Client ID list */ + tmp = silc_argument_get_arg_type(cmd->args, 4, &len2); + if (!tmp) { + cmd->client->ops->say(cmd->client, conn, + "Cannot get user list: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + + client_id_list = silc_buffer_alloc(len2); + silc_buffer_pull_tail(client_id_list, len2); + silc_buffer_put(client_id_list, tmp, len2); + + /* Get client mode list */ + tmp = silc_argument_get_arg_type(cmd->args, 5, &len2); + if (!tmp) { + cmd->client->ops->say(cmd->client, conn, + "Cannot get user list: Bad reply packet"); + COMMAND_REPLY_ERROR; + goto out; + } + + client_mode_list = silc_buffer_alloc(len2); + silc_buffer_pull_tail(client_mode_list, len2); + silc_buffer_put(client_mode_list, tmp, len2); + + /* Get the channel name */ + if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id, + SILC_ID_CHANNEL, &id_cache)) { + COMMAND_REPLY_ERROR; + goto out; + } + + channel = (SilcChannelEntry)id_cache->context; + + /* If there is pending command we know that user has called this command + and we will handle the name list differently. */ + if (cmd->callback) { + /* We will resolve all the necessary information about the people + on the channel. Only after that will we display the user list. */ + for (i = 0; i < len1; i++) { + /* XXX */ + + } + silc_client_command_pending_del(SILC_COMMAND_NAMES); + } else { + /* there is no pending callback it means that this command reply + has been received without calling the command, ie. server has sent + the reply without getting the command from us first. This happens + with SILC servers that sends NAMES reply after joining to a channel. */ + + /* Remove commas from list */ + for (i = 0; i < len1; i++) + if (name_list[i] == ',') { + name_list[i] = ' '; + list_count++; + } + list_count++; + } + + /* Remove old client list from channel, if exists */ + if (channel->clients) { + silc_free(channel->clients); + channel->clients = NULL; + channel->clients_count = 0; + } + + /* Allocate room for clients in the channel */ + channel->clients = silc_calloc(list_count, sizeof(*channel->clients)); + + /* Cache the received name list, client ID's and modes. This cache expires + whenever server sends notify message to channel. It means two things; + some user has joined or leaved the channel. */ + cp = name_list; + for (i = 0; i < list_count; i++) { + int nick_len = strcspn(name_list, " "); + unsigned short idp_len; + unsigned int mode; + char *nickname = silc_calloc(nick_len + 1, sizeof(*nickname)); + SilcClientID *client_id; + SilcClientEntry client; + + memcpy(nickname, name_list, nick_len); + SILC_GET16_MSB(idp_len, client_id_list->data + 2); + idp_len += 4; + client_id = silc_id_payload_parse_id(client_id_list->data, idp_len); + silc_buffer_pull(client_id_list, idp_len); + + SILC_GET32_MSB(mode, client_mode_list->data); + silc_buffer_pull(client_mode_list, 4); + + /* Check if we have this client cached already. */ + if (!silc_idcache_find_by_id_one(conn->client_cache, (void *)client_id, + SILC_ID_CLIENT, &id_cache)) { + client = silc_calloc(1, sizeof(*client)); + client->id = client_id; + silc_parse_nickname(nickname, &client->nickname, &client->server, + &client->num); + silc_free(nickname); + + /* Add client to cache */ + silc_idcache_add(conn->client_cache, client->nickname, SILC_ID_CLIENT, + client_id, (void *)client, TRUE); + } else { + client = (SilcClientEntry)id_cache->context; + silc_free(client_id); + silc_free(nickname); + id_cache = NULL; + } + + channel->clients[channel->clients_count].client = client; + channel->clients[channel->clients_count].mode = mode; + channel->clients_count++; + + name_list += nick_len + 1; + } + + name_list = cp; + for (i = 0; i < list_count; i++) { + int c; + int nick_len = strcspn(name_list, " "); + char *nickname = silc_calloc(nick_len + 1, sizeof(*nickname)); + memcpy(nickname, name_list, nick_len); + + for (c = 0, k = 0; k < channel->clients_count; k++) { + if (channel->clients[k].client && + !strncmp(channel->clients[k].client->nickname, + nickname, strlen(channel->clients[k].client->nickname))) { + char t[8]; + + if (!c) { + c++; + continue; + } + + memset(t, 0, sizeof(t)); + channel->clients[k].client->nickname = + silc_calloc(strlen(nickname) + 8, sizeof(*channel->clients[k]. + client->nickname)); ++ snprintf(t, sizeof(t), "[%d]", c++); ++ strncat(channel->clients[k].client->nickname, t, strlen(t)); + strncat(channel->clients[k].client->nickname, nickname, + strlen(nickname)); - snprintf(t, sizeof(t), " [%d]", c++); - strncat(channel->clients[k].client->nickname, t, strlen(t)); + } + } + + silc_free(nickname); + } + + name_list = NULL; + len1 = 0; + for (k = 0; k < channel->clients_count; k++) { - char *n = channel->clients[k].client->nickname; ++ char *m, *n = channel->clients[k].client->nickname; + len2 = strlen(n); + len1 += len2; - name_list = silc_realloc(name_list, sizeof(*name_list) * (len1 + 1)); ++ ++ name_list = silc_realloc(name_list, sizeof(*name_list) * (len1 + 3)); ++ ++ m = silc_client_chumode_char(channel->clients[k].mode); ++ if (m) { ++ memcpy(name_list + (len1 - len2), m, strlen(m)); ++ len1 += strlen(m); ++ silc_free(m); ++ } ++ + memcpy(name_list + (len1 - len2), n, len2); + name_list[len1] = 0; + + if (k == channel->clients_count - 1) + break; + memcpy(name_list + len1, " ", 1); + len1++; + } + + cmd->client->ops->say(cmd->client, conn, + "Users on %s: %s", channel->channel_name, name_list); + + /* Notify application */ + COMMAND_REPLY((ARGS, channel, name_list, client_id_list->head, + client_mode_list->head)); + + silc_free(name_list); + silc_buffer_free(client_id_list); + silc_buffer_free(client_mode_list); + + out: + if (channel_id) + silc_free(channel_id); + silc_client_command_reply_free(cmd); +}